blob: cdf21b1e7a78a954012ff99e70d0baf23ed060af [file] [log] [blame]
/*
* teamd_dbus.c - Teamd dbus api
* 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 "config.h"
#ifdef ENABLE_DBUS
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <dbus/dbus.h>
#include <private/misc.h>
#include <team.h>
#include "teamd.h"
#include "teamd_dbus.h"
#include "teamd_dbus_common.h"
#include "teamd_ctl.h"
static const char *introspection_xml =
"<node name='" TEAMD_DBUS_PATH "'>"
" <interface name='" TEAMD_DBUS_IFACE "'>"
" <method name='PortConfigUpdate'>"
" <arg type='s' name='port_devname' direction='in'/>"
" <arg type='s' name='port_config' direction='in'/>"
" </method>"
" <method name='PortConfigDump'>"
" <arg type='s' name='port_devname' direction='in'/>"
" </method>"
" <method name='PortAdd'>"
" <arg type='s' name='port_devname' direction='in'/>"
" </method>"
" <method name='PortRemove'>"
" <arg type='s' name='port_devname' direction='in'/>"
" </method>"
" <method name='ConfigDump'>"
" </method>"
" <method name='ConfigDumpActual'>"
" </method>"
" <method name='StateDump'>"
" </method>"
" <method name='StateItemValueGet'>"
" <arg type='s' name='state_item_path' direction='in'/>"
" </method>"
" <method name='StateItemValueSet'>"
" <arg type='s' name='state_item_path' direction='in'/>"
" <arg type='s' name='value' direction='in'/>"
" </method>"
" </interface>"
"</node>";
static DBusMessage *introspect(DBusMessage *message)
{
DBusMessage *reply;
reply = dbus_message_new_method_return(message);
if (!reply)
return NULL;
dbus_message_append_args(reply, DBUS_TYPE_STRING, &introspection_xml,
DBUS_TYPE_INVALID);
return reply;
}
struct dbus_ops_priv {
DBusMessage *reply;
DBusMessage *message;
};
static int dbus_op_get_args(void *ops_priv, const char *fmt, ...)
{
va_list ap;
struct dbus_ops_priv *dbus_ops_priv = ops_priv;
DBusMessage *message = dbus_ops_priv->message;
DBusMessageIter iter;
int arg_type;
char **pstr;
int err = 0;
dbus_message_iter_init(message, &iter);
va_start(ap, fmt);
while (*fmt) {
arg_type = dbus_message_iter_get_arg_type(&iter);
if (arg_type == DBUS_TYPE_INVALID) {
teamd_log_err("Insufficient number of arguments in message.");
err = -EINVAL;
goto out;
}
switch (*fmt++) {
case 's': /* string */
if (arg_type != DBUS_TYPE_STRING) {
teamd_log_err("Unexpected argument type found in message.");
err = -EINVAL;
goto out;
}
pstr = va_arg(ap, char **);
dbus_message_iter_get_basic(&iter, pstr);
break;
default:
teamd_log_err("Unknown argument type requested");
err = -EINVAL;
goto out;
}
dbus_message_iter_next(&iter);
}
out:
va_end(ap);
return err;
}
static int dbus_op_reply_err(void *ops_priv, const char *err_code,
const char *err_msg)
{
struct dbus_ops_priv *dbus_ops_priv = ops_priv;
int err;
char *err_code_buf;
err = asprintf(&err_code_buf, TEAMD_DBUS_IFACE "%s", err_code);
if (err == -1)
return -errno;
dbus_ops_priv->reply = dbus_message_new_error(dbus_ops_priv->message,
err_code_buf,
err_msg);
free(err_code_buf);
return 0;
}
static int dbus_op_reply_succ(void *ops_priv, const char *msg)
{
struct dbus_ops_priv *dbus_ops_priv = ops_priv;
DBusMessage *reply;
if (!msg)
return 0;
reply = dbus_message_new_method_return(dbus_ops_priv->message);
if (reply)
dbus_message_append_args(reply, DBUS_TYPE_STRING, &msg,
DBUS_TYPE_INVALID);
dbus_ops_priv->reply = reply;
return 0;
}
static const struct teamd_ctl_method_ops teamd_dbus_ctl_method_ops = {
.get_args = dbus_op_get_args,
.reply_err = dbus_op_reply_err,
.reply_succ = dbus_op_reply_succ,
};
static DBusHandlerResult message_handler(DBusConnection *con,
DBusMessage *message,
void *user_data)
{
const char *method;
const char *path;
const char *msg_interface;
DBusMessage *reply = NULL;
struct teamd_context *ctx = user_data;
method = dbus_message_get_member(message);
path = dbus_message_get_path(message);
msg_interface = dbus_message_get_interface(message);
if (!method || !path || !msg_interface)
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
teamd_log_dbg("dbus: %s.%s (%s)", msg_interface, method, path);
if (!strcmp(method, "Introspect") &&
!strcmp(msg_interface, "org.freedesktop.DBus.Introspectable")) {
reply = introspect(message);
}
if (!strcmp(msg_interface, TEAMD_DBUS_IFACE) &&
teamd_ctl_method_exists(method)) {
struct dbus_ops_priv dbus_ops_priv;
dbus_ops_priv.reply = NULL;
dbus_ops_priv.message = message;
teamd_ctl_method_call(ctx, method, &teamd_dbus_ctl_method_ops,
&dbus_ops_priv);
reply = dbus_ops_priv.reply;
}
if (!dbus_message_get_no_reply(message)) {
if (!reply)
reply = dbus_message_new_method_return(message);
if (reply) {
if (!dbus_connection_send(con, reply, NULL))
teamd_log_err("dbus: Failed to send reply.");
dbus_message_unref(reply);
}
}
return DBUS_HANDLER_RESULT_HANDLED;
}
static const DBusObjectPathVTable vtable = {
.message_function = message_handler,
};
static int teamd_dbus_iface_init(struct teamd_context *ctx)
{
if (dbus_connection_register_object_path(ctx->dbus.con,
TEAMD_DBUS_PATH, &vtable,
ctx) == FALSE) {
teamd_log_err("dbus: Could not set up message handler");
return -EINVAL;
}
return 0;
}
static void teamd_dbus_iface_fini(struct teamd_context *ctx)
{
dbus_connection_unregister_object_path(ctx->dbus.con,
TEAMD_DBUS_PATH);
}
static int teamd_dbus_con_init(struct teamd_context *ctx)
{
DBusError error;
int err = 0;
dbus_error_init(&error);
ctx->dbus.con = dbus_bus_get(DBUS_BUS_SYSTEM, &error);
if (!ctx->dbus.con) {
teamd_log_err("dbus: Could not acquire the system bus: %s - %s",
error.name, error.message);
err = -EINVAL;
goto free_err;
}
dbus_connection_set_exit_on_disconnect(ctx->dbus.con, FALSE);
free_err:
dbus_error_free(&error);
return err;
}
static void teamd_dbus_con_fini(struct teamd_context *ctx)
{
dbus_connection_unref(ctx->dbus.con);
}
static int callback_watch(struct teamd_context *ctx, int events, void *priv)
{
DBusWatch *watch = priv;
dbus_connection_ref(ctx->dbus.con);
if (events & TEAMD_LOOP_FD_EVENT_READ)
dbus_watch_handle(watch, DBUS_WATCH_READABLE);
if (events & TEAMD_LOOP_FD_EVENT_WRITE)
dbus_watch_handle(watch, DBUS_WATCH_WRITABLE);
if (events & TEAMD_LOOP_FD_EVENT_EXCEPTION)
dbus_watch_handle(watch, DBUS_WATCH_ERROR);
dbus_connection_unref(ctx->dbus.con);
return 0;
}
#define WATCH_CB_NAME "dbus_watch"
static dbus_bool_t add_watch(DBusWatch *watch, void *priv)
{
struct teamd_context *ctx = priv;
unsigned int flags;
int err;
int fd;
int fd_events;
fd = dbus_watch_get_unix_fd(watch);
flags = dbus_watch_get_flags(watch);
fd_events = TEAMD_LOOP_FD_EVENT_EXCEPTION;
if (flags & DBUS_WATCH_READABLE)
fd_events |= TEAMD_LOOP_FD_EVENT_READ;
if (flags & DBUS_WATCH_WRITABLE)
fd_events |= TEAMD_LOOP_FD_EVENT_WRITE;
err = teamd_loop_callback_fd_add(ctx, WATCH_CB_NAME, watch,
callback_watch, fd, fd_events);
if (err)
return FALSE;
if (dbus_watch_get_enabled(watch))
teamd_loop_callback_enable(ctx, WATCH_CB_NAME, watch);
return TRUE;
}
static void remove_watch(DBusWatch *watch, void *priv)
{
struct teamd_context *ctx = priv;
teamd_loop_callback_del(ctx, WATCH_CB_NAME, watch);
}
static void toggle_watch(DBusWatch *watch, void *priv)
{
struct teamd_context *ctx = priv;
if (dbus_watch_get_enabled(watch))
teamd_loop_callback_enable(ctx, WATCH_CB_NAME, watch);
else
teamd_loop_callback_disable(ctx, WATCH_CB_NAME, watch);
}
static int callback_timeout(struct teamd_context *ctx, int events, void *priv)
{
DBusTimeout *timeout = priv;
dbus_timeout_handle(timeout);
return 0;
}
#define TIMEOUT_CB_NAME "dbus_timeout"
static dbus_bool_t add_timeout(DBusTimeout *timeout, void *priv)
{
struct teamd_context *ctx = priv;
int err;
struct timespec ts;
ms_to_timespec(&ts, dbus_timeout_get_interval(timeout));
err = teamd_loop_callback_timer_add_set(ctx, TIMEOUT_CB_NAME, timeout,
callback_timeout, NULL, &ts);
if (err)
return FALSE;
if (dbus_timeout_get_enabled(timeout))
teamd_loop_callback_enable(ctx, TIMEOUT_CB_NAME, timeout);
return TRUE;
}
static void remove_timeout(DBusTimeout *timeout, void *priv)
{
struct teamd_context *ctx = priv;
teamd_loop_callback_del(ctx, TIMEOUT_CB_NAME, timeout);
}
static void toggle_timeout(DBusTimeout *timeout, void *priv)
{
struct teamd_context *ctx = priv;
if (dbus_timeout_get_enabled(timeout))
teamd_loop_callback_enable(ctx, TIMEOUT_CB_NAME, timeout);
else
teamd_loop_callback_disable(ctx, TIMEOUT_CB_NAME, timeout);
}
static void wakeup_main(void *priv)
{
struct teamd_context *ctx = priv;
teamd_run_loop_restart(ctx);
}
struct dispatch_priv {
int fd_r;
int fd_w;
struct teamd_context *ctx;
};
static int callback_dispatch(struct teamd_context *ctx, int events, void *priv)
{
struct dispatch_priv *dp = priv;
char byte;
int err;
err = read(dp->fd_r, &byte, 1);
if (err == -1) {
if (errno != EINTR && errno != EAGAIN) {
teamd_log_err("dbus: dispatch, read() failed.");
return -errno;
}
} else {
while (dbus_connection_dispatch(ctx->dbus.con) ==
DBUS_DISPATCH_DATA_REMAINS);
}
return 0;
}
static void wakeup_dispatch(struct dispatch_priv *dp)
{
int err;
retry:
err = write(dp->fd_w, "a", 1);
if (err == -1 && errno == EINTR)
goto retry;
}
static void dispatch_status(DBusConnection *conn, DBusDispatchStatus status,
void *priv) {
struct dispatch_priv *dp = priv;
if (status == DBUS_DISPATCH_COMPLETE)
return;
wakeup_dispatch(dp);
}
#define DISPATCH_CB_NAME "dbus_dispatch"
static int dispatch_init(struct dispatch_priv **pdp, struct teamd_context *ctx)
{
struct dispatch_priv *dp;
int fds[2];
int err;
dp = myzalloc(sizeof(*dp));
if (!dp)
return -ENOMEM;
err = pipe(fds);
if (err) {
err = -errno;
goto free_dp;
}
dp->fd_r = fds[0];
dp->fd_w = fds[1];
dp->ctx = ctx;
err = teamd_loop_callback_fd_add(ctx, DISPATCH_CB_NAME, dp,
callback_dispatch,
dp->fd_r, TEAMD_LOOP_FD_EVENT_READ);
teamd_loop_callback_enable(ctx, DISPATCH_CB_NAME, dp);
if (err)
goto close_pipe;
*pdp = dp;
return 0;
close_pipe:
close(dp->fd_w);
close(dp->fd_r);
free_dp:
free(dp);
return err;
}
static void dispatch_exit(void *priv)
{
struct dispatch_priv *dp = priv;
teamd_loop_callback_del(dp->ctx, DISPATCH_CB_NAME, dp);
close(dp->fd_w);
close(dp->fd_r);
free(dp);
}
static int teamd_dbus_mainloop_init(struct teamd_context *ctx)
{
struct dispatch_priv *dp = NULL; /* gcc needs this initialized */
int err;
err = dispatch_init(&dp, ctx);
if (err) {
teamd_log_err("dbus: failed to init dispatch.");
return err;
}
dbus_connection_set_dispatch_status_function(ctx->dbus.con,
dispatch_status,
dp, dispatch_exit);
if (dbus_connection_set_watch_functions(ctx->dbus.con, add_watch,
remove_watch, toggle_watch,
ctx, NULL) == FALSE) {
teamd_log_err("dbus: failed to init watch functions.");
return -EINVAL;
}
if (dbus_connection_set_timeout_functions(ctx->dbus.con, add_timeout,
remove_timeout,
toggle_timeout,
ctx, NULL) == FALSE) {
teamd_log_err("dbus: failed to init timeout functions.");
return -EINVAL;
}
dbus_connection_set_wakeup_main_function(ctx->dbus.con, wakeup_main,
ctx, NULL);
/* Do initial dispatch for early messages */
wakeup_dispatch(dp);
return 0;
}
int teamd_dbus_init(struct teamd_context *ctx)
{
int err;
char *id;
if (!ctx->dbus.enabled)
return 0;
err = teamd_dbus_con_init(ctx);
if (err)
return err;
err = teamd_dbus_iface_init(ctx);
if (err)
goto con_fini;
err = teamd_dbus_mainloop_init(ctx);
if (err)
goto iface_fini;
id = dbus_connection_get_server_id(ctx->dbus.con),
teamd_log_dbg("dbus: connected to %s with name %s", id,
dbus_bus_get_unique_name(ctx->dbus.con));
dbus_free(id);
return 0;
iface_fini:
teamd_dbus_iface_fini(ctx);
con_fini:
teamd_dbus_con_fini(ctx);
return err;
}
void teamd_dbus_fini(struct teamd_context *ctx)
{
if (!ctx->dbus.enabled)
return;
teamd_dbus_iface_fini(ctx);
teamd_dbus_con_fini(ctx);
}
int teamd_dbus_expose_name(struct teamd_context *ctx)
{
DBusError error;
int err;
char *service_name;
if (!ctx->dbus.enabled)
return 0;
err = asprintf(&service_name, TEAMD_DBUS_SERVICE ".%s",
ctx->team_devname);
if (err == -1)
return -errno;
dbus_error_init(&error);
err = dbus_bus_request_name(ctx->dbus.con, service_name, 0,
&error);
if (err == DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) {
teamd_log_dbg("dbus: have name %s", service_name);
err = 0;
} else if (dbus_error_is_set(&error)) {
teamd_log_err("dbus: Failed to acquire %s: %s: %s",
service_name, error.name, error.message);
err = -EINVAL;
} else {
teamd_log_err("dbus: name %s already taken.", service_name);
err = -EBUSY;
}
dbus_error_free(&error);
free(service_name);
return err;
}
#endif /* ENABLE_DBUS */