/*
* Soft: Keepalived is a failover program for the LVS project
* <www.linuxvirtualserver.org>. It monitor & manipulate
* a loadbalanced server pool using multi-layer checks.
*
* Part: DBus server thread for VRRP
*
* Author: Alexandre Cassen, <acassen@linux-vs.org>
*
* This program 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 General Public License for more details.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version
* 2 of the License, or (at your option) any later version.
*
* Copyright (C) 2016-2017 Alexandre Cassen, <acassen@gmail.com>
*/
/* See https://git.gnome.org/browse/glib/tree/gio/tests/fdbus-example-server.c
* and https://developer.gnome.org/gio/stable/GDBusConnection.html#gdbus-server
* for examples of coding.
*
* Create a general /org/keepalived/Vrrp1/Vrrp DBus
* object and a /org/keepalived/Vrrp1/Instance/#interface#/#group# object for
* each VRRP instance.
* Interface org.keepalived.Vrrp1.Vrrp implements methods PrintData,
* PrintStats and signal VrrpStopped.
* Interface com.keepalived.Vrrp1.Instance implements method SendGarp
* (sends a single Gratuitous ARP from the given Instance),
* signal VrrpStatusChange, and properties Name and State (retrievable
* through calls to org.freedesktop.DBus.Properties.Get)
*
* Interface files need to be installed in /usr/share/dbus-1/interfaces/
* A policy file, which determines who has access to the service, is
* installed in /etc/dbus-1/system.d/. Sources for the policy and interface
* files are in keepalived/dbus.
*
* To test the DBus service run a command like: dbus-send --system --dest=org.keepalived.Vrrp1 --print-reply object interface.method type:argument
* e.g.
* dbus-send --system --dest=org.keepalived.Vrrp1 --print-reply /org/keepalived/Vrrp1/Vrrp org.keepalived.Vrrp1.Vrrp.PrintData
* or
* dbus-send --system --dest=org.keepalived.Vrrp1 --print-reply /org/keepalived/Vrrp1/Instance/eth0/1/IPv4 org.freedesktop.DBus.Properties.Get string:'org.keepalived.Vrrp1.Instance' string:'State'
*
* To monitor signals, run:
* dbus-monitor --system type='signal'
*
* d-feet is a useful program for interfacing with DBus
*/
#include "config.h"
#include <errno.h>
#include <pthread.h>
#include <semaphore.h>
#include <gio/gio.h>
#include <ctype.h>
#include <fcntl.h>
#include <sys/types.h>
#include <stdlib.h>
#include <stdint.h>
#include "vrrp_dbus.h"
#include "vrrp_data.h"
#include "vrrp_print.h"
#include "global_data.h"
#include "main.h"
#include "logger.h"
#include "utils.h"
#ifdef THREAD_DUMP
#include "scheduler.h"
#endif
typedef enum dbus_action {
DBUS_ACTION_NONE,
DBUS_PRINT_DATA,
DBUS_PRINT_STATS,
DBUS_RELOAD,
#ifdef _WITH_DBUS_CREATE_INSTANCE_
DBUS_CREATE_INSTANCE,
DBUS_DESTROY_INSTANCE,
#endif
DBUS_SEND_GARP,
DBUS_GET_NAME,
DBUS_GET_STATUS,
} dbus_action_t;
typedef enum dbus_error {
DBUS_SUCCESS,
DBUS_INTERFACE_NOT_FOUND,
DBUS_OBJECT_ALREADY_EXISTS,
DBUS_INTERFACE_TOO_LONG,
DBUS_INSTANCE_NOT_FOUND,
} dbus_error_t;
typedef struct dbus_queue_ent {
dbus_action_t action;
dbus_error_t reply;
char *ifname;
uint8_t vrid;
uint8_t family;
GVariant *args;
} dbus_queue_ent_t;
/* Global file variables */
static GDBusNodeInfo *vrrp_introspection_data = NULL;
static GDBusNodeInfo *vrrp_instance_introspection_data = NULL;
static GDBusConnection *global_connection;
static GHashTable *objects;
static GMainLoop *loop;
/* Data passing between main vrrp thread and dbus thread */
dbus_queue_ent_t *ent_ptr;
static int dbus_in_pipe[2], dbus_out_pipe[2];
static sem_t thread_end;
/* The only characters that are valid in a dbus path are A-Z, a-z, 0-9, _ */
static char *
set_valid_path(char *valid_path, const char *path)
{
const char *str_in;
char *str_out;
for (str_in = path, str_out = valid_path; *str_in; str_in++, str_out++) {
if (!isalnum(*str_in))
*str_out = '_';
else
*str_out = *str_in;
}
*str_out = '\0';
return valid_path;
}
static bool
valid_path_cmp(const char *path, const char *valid_path)
{
for ( ; *path && *valid_path; path++, valid_path++) {
if (!isalnum(*path)) {
if (*valid_path != '_')
return true;
}
else if (*path != *valid_path)
return true;
}
return *path != *valid_path;
}
static const char *
family_str(int family)
{
if (family == AF_INET)
return "IPv4";
if (family == AF_INET6)
return "IPv6";
return "None";
}
static const char *
state_str(int state)
{
switch (state) {
case VRRP_STATE_INIT:
return "Init";
case VRRP_STATE_BACK:
return "Backup";
case VRRP_STATE_MAST:
return "Master";
case VRRP_STATE_FAULT:
return "Fault";
}
return "Unknown";
}
static vrrp_t *
get_vrrp_instance(const char *ifname, int vrid, int family)
{
element e;
vrrp_t *vrrp;
if (LIST_ISEMPTY(vrrp_data->vrrp))
return NULL;
for (e = LIST_HEAD(vrrp_data->vrrp); e; ELEMENT_NEXT(e)) {
vrrp = ELEMENT_DATA(e);
if (vrrp->vrid == vrid &&
vrrp->family == family &&
!valid_path_cmp(IF_BASE_IFP(vrrp->ifp)->ifname, ifname))
return vrrp;
}
return NULL;
}
static gboolean
unregister_object(gpointer key, gpointer value, __attribute__((unused)) gpointer user_data)
{
if (g_hash_table_remove(objects, key))
return g_dbus_connection_unregister_object(global_connection, GPOINTER_TO_UINT(value));
return false;
}
static gchar *
dbus_object_create_path_vrrp(void)
{
return g_strconcat(DBUS_VRRP_OBJECT_ROOT,
#if HAVE_DECL_CLONE_NEWNET
global_data->network_namespace ? "/" : "", global_data->network_namespace ? global_data->network_namespace : "",
#endif
global_data->instance_name ? "/" : "", global_data->instance_name ? global_data->instance_name : "",
"/Vrrp", NULL);
}
static gchar *
dbus_object_create_path_instance(const gchar *interface, int vrid, sa_family_t family)
{
gchar *object_path;
char standardized_name[sizeof ((vrrp_t*)NULL)->ifp->ifname];
gchar *vrid_str = g_strdup_printf("%d", vrid);
set_valid_path(standardized_name, interface);
object_path = g_strconcat(DBUS_VRRP_OBJECT_ROOT,
#if HAVE_DECL_CLONE_NEWNET
global_data->network_namespace ? "/" : "", global_data->network_namespace ? global_data->network_namespace : "",
#endif
global_data->instance_name ? "/" : "", global_data->instance_name ? global_data->instance_name : "",
"/Instance/",
standardized_name, "/", vrid_str,
"/", family_str(family),
NULL);
g_free(vrid_str);
return object_path;
}
static dbus_queue_ent_t *
process_method_call(dbus_queue_ent_t *ent)
{
ssize_t ret;
if (!ent)
return NULL;
/* Tell the main thread that a queue entry is waiting. Any data works */
ent_ptr = ent;
if (write(dbus_in_pipe[1], ent, 1) != 1)
log_message(LOG_INFO, "Write from DBus thread to main thread failed");
/* Wait for a response */
while ((ret = read(dbus_out_pipe[0], ent, 1)) == -1 && errno == EINTR) {
log_message(LOG_INFO, "dbus_out_pipe read returned EINTR");
}
if (ret == -1)
log_message(LOG_INFO, "DBus response read error - errno = %d", errno);
#ifdef DBUS_DEBUG
if (ent->reply != DBUS_SUCCESS) {
char *iname;
if (ent->reply == DBUS_INTERFACE_NOT_FOUND)
log_message(LOG_INFO, "Unable to find DBus requested instance %s/%d/%s", ent->ifname, ent->vrid, family_str(ent->family));
else if (ent->reply == DBUS_OBJECT_ALREADY_EXISTS)
log_message(LOG_INFO, "Unable to create DBus requested object with instance %s/%d/%s", ent->ifname, ent->vrid, family_str(ent->family));
else if (ent->reply == DBUS_INSTANCE_NOT_FOUND) {
g_variant_get(ent->args, "(s)", &iname);
log_message(LOG_INFO, "Unable to find DBus requested instance %s", iname);
}
else
log_message(LOG_INFO, "Unknown DBus reply %d", ent->reply);
}
#endif
return ent;
}
static void
get_interface_ids(const gchar *object_path, gchar *interface, uint8_t *vrid, uint8_t *family)
{
int path_length = DBUS_VRRP_INSTANCE_PATH_DEFAULT_LENGTH;
gchar **dirs;
char *endptr;
#if HAVE_DECL_CLONE_NEWNET
if(global_data->network_namespace)
path_length++;
#endif
if(global_data->instance_name)
path_length++;
/* object_path will have interface, vrid and family as
* the third to last, second to last and last levels */
dirs = g_strsplit(object_path, "/", path_length);
strcpy(interface, dirs[path_length-3]);
*vrid = (uint8_t)strtoul(dirs[path_length-2], &endptr, 10);
if (*endptr)
log_message(LOG_INFO, "Dbus unexpected characters '%s' at end of number '%s'", endptr, dirs[path_length-2]);
*family = !g_strcmp0(dirs[path_length-1], "IPv4") ? AF_INET : !g_strcmp0(dirs[path_length-1], "IPv6") ? AF_INET6 : AF_UNSPEC;
/* We are finished with all the object_path strings now */
g_strfreev(dirs);
}
/* handles reply to org.freedesktop.DBus.Properties.Get method on any object*/
static GVariant *
handle_get_property(__attribute__((unused)) GDBusConnection *connection,
__attribute__((unused)) const gchar *sender,
const gchar *object_path,
const gchar *interface_name,
const gchar *property_name,
GError **error,
__attribute__((unused)) gpointer user_data)
{
GVariant *ret = NULL;
dbus_queue_ent_t ent;
char ifname_str[sizeof ((vrrp_t*)NULL)->ifp->ifname];
int action;
if (g_strcmp0(interface_name, DBUS_VRRP_INSTANCE_INTERFACE)) {
log_message(LOG_INFO, "Interface %s has not been implemented yet", interface_name);
return NULL;
}
if (!g_strcmp0(property_name, "Name"))
action = DBUS_GET_NAME;
else if (!g_strcmp0(property_name, "State"))
action = DBUS_GET_STATUS;
else {
log_message(LOG_INFO, "Property %s does not exist", property_name);
return NULL;
}
get_interface_ids(object_path, ifname_str, &ent.vrid, &ent.family);
ent.action = action;
ent.ifname = ifname_str;
ent.args = NULL;
process_method_call(&ent);
if (ent.reply == DBUS_SUCCESS)
ret = ent.args;
else
g_set_error(error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, "Instance '%s/%d/%s' not found", ifname_str, ent.vrid, family_str(ent.family));
return ret;
}
/* handles method_calls on any object */
static void
handle_method_call(__attribute__((unused)) GDBusConnection *connection,
__attribute__((unused)) const gchar *sender,
const gchar *object_path,
const gchar *interface_name,
const gchar *method_name,
#ifndef _WITH_DBUS_CREATE_INSTANCE_
__attribute__((unused))
#endif
GVariant *parameters,
GDBusMethodInvocation *invocation,
__attribute__((unused)) gpointer user_data)
{
#ifdef _WITH_DBUS_CREATE_INSTANCE_
char *iname;
char *ifname;
size_t len;
unsigned family;
#endif
dbus_queue_ent_t ent;
char ifname_str[sizeof ((vrrp_t*)NULL)->ifp->ifname];
if (!g_strcmp0(interface_name, DBUS_VRRP_INTERFACE)) {
if (!g_strcmp0(method_name, "PrintData")) {
ent.action = DBUS_PRINT_DATA;
process_method_call(&ent);
g_dbus_method_invocation_return_value(invocation, NULL);
}
else if (g_strcmp0(method_name, "PrintStats") == 0) {
ent.action = DBUS_PRINT_STATS;
process_method_call(&ent);
g_dbus_method_invocation_return_value(invocation, NULL);
}
else if (g_strcmp0(method_name, "ReloadConfig") == 0) {
g_dbus_method_invocation_return_value(invocation, NULL);
kill(getppid(), SIGHUP);
}
#ifdef _WITH_DBUS_CREATE_INSTANCE_
else if (g_strcmp0(method_name, "CreateInstance") == 0) {
g_variant_get(parameters, "(ssuu)", &iname, &ifname, &ent.vrid, &family);
len = strlen(ifname);
if (len == 0 || len >= IFNAMSIZ) {
log_message(LOG_INFO, "Interface name '%s' too long for CreateInstance", ifname);
g_dbus_method_invocation_return_error(invocation, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, "Interface name empty or too long");
return;
}
ent.action = DBUS_CREATE_INSTANCE;
ent.ifname = ifname;
ent.family = family == 4 ? AF_INET : family == 6 ? AF_INET6 : AF_UNSPEC;
ent.args = g_variant_new("(s)", iname);
process_method_call(&ent);
g_variant_unref(ent.args);
g_dbus_method_invocation_return_value(invocation, NULL);
}
else if (g_strcmp0(method_name, "DestroyInstance") == 0) {
// TODO - this should be on the instance
ent.action = DBUS_DESTROY_INSTANCE;
ent.args = parameters;
process_method_call(&ent);
if (ent.reply == DBUS_INSTANCE_NOT_FOUND) {
g_variant_get(parameters, "(s)", &iname);
g_dbus_method_invocation_return_error(invocation, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, "Instance '%s' not found", iname);
}
else
g_dbus_method_invocation_return_value(invocation, NULL);
}
#endif
else {
log_message(LOG_INFO, "Method %s has not been implemented yet", method_name);
g_dbus_method_invocation_return_error(invocation, G_DBUS_ERROR, G_DBUS_ERROR_MATCH_RULE_NOT_FOUND, "Method not implemented");
}
return;
}
if (!g_strcmp0(interface_name, DBUS_VRRP_INSTANCE_INTERFACE)) {
if (!g_strcmp0(method_name, "SendGarp")) {
get_interface_ids(object_path, ifname_str, &ent.vrid, &ent.family);
ent.action = DBUS_SEND_GARP;
ent.ifname = ifname_str;
process_method_call(&ent);
if (ent.reply == DBUS_INTERFACE_NOT_FOUND)
g_dbus_method_invocation_return_error(invocation, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, "Instance '%s/%d/%s' not found", ifname_str, ent.vrid, family_str(ent.family));
else
g_dbus_method_invocation_return_value(invocation, NULL);
} else {
log_message(LOG_INFO, "Method %s has not been implemented yet", method_name);
g_dbus_method_invocation_return_error(invocation, G_DBUS_ERROR, G_DBUS_ERROR_MATCH_RULE_NOT_FOUND, "Method not implemented");
}
return;
}
log_message(LOG_INFO, "Interface %s has not been implemented yet", interface_name);
g_dbus_method_invocation_return_error(invocation, G_DBUS_ERROR, G_DBUS_ERROR_MATCH_RULE_NOT_FOUND, "Interface not implemented");
}
static const GDBusInterfaceVTable interface_vtable =
{
handle_method_call,
handle_get_property,
NULL, /* handle_set_property is null because we have no writeable property */
{}
};
static int
dbus_create_object_params(char *instance_name, const char *interface_name, int vrid, sa_family_t family, bool log_success)
{
gchar *object_path;
GError *local_error = NULL;
if (g_hash_table_lookup(objects, instance_name)) {
log_message(LOG_INFO, "An object for instance %s already exists", instance_name);
return DBUS_OBJECT_ALREADY_EXISTS;
}
object_path = dbus_object_create_path_instance(interface_name, vrid, family);
guint instance = g_dbus_connection_register_object(global_connection, object_path,
vrrp_instance_introspection_data->interfaces[0],
&interface_vtable, NULL, NULL, &local_error);
if (local_error != NULL) {
log_message(LOG_INFO, "Registering DBus object on %s failed: %s",
object_path, local_error->message);
g_clear_error(&local_error);
}
if (instance) {
g_hash_table_insert(objects, instance_name, GUINT_TO_POINTER(instance));
if (log_success)
log_message(LOG_INFO, "Added DBus object for instance %s on path %s", instance_name, object_path);
}
g_free(object_path);
return DBUS_SUCCESS;
}
static void
dbus_create_object(vrrp_t *vrrp)
{
dbus_create_object_params(vrrp->iname, IF_NAME(IF_BASE_IFP(vrrp->ifp)), vrrp->vrid, vrrp->family, false);
}
static bool
dbus_emit_signal(GDBusConnection *connection,
const gchar *object_path,
const gchar *interface_name,
const gchar *signal_name,
GVariant *parameters)
{
GError *local_error = NULL;
g_dbus_connection_emit_signal(connection, NULL, object_path, interface_name, signal_name, parameters,
&local_error);
if (local_error != NULL) {
log_message(LOG_INFO, "Emitting DBus signal %s.%s on %s failed: %s",
interface_name, signal_name, object_path, local_error->message);
g_clear_error(&local_error);
return false;
}
return true;
}
/* first function to be run when trying to own bus,
* exports objects to the bus */
static void
on_bus_acquired(GDBusConnection *connection,
const gchar *name,
__attribute__((unused)) gpointer user_data)
{
global_connection = connection;
gchar *path;
element e;
GError *local_error = NULL;
log_message(LOG_INFO, "Acquired DBus bus %s", name);
/* register VRRP object */
path = dbus_object_create_path_vrrp();
guint vrrp = g_dbus_connection_register_object(connection, path,
vrrp_introspection_data->interfaces[0],
&interface_vtable, NULL, NULL, &local_error);
g_hash_table_insert(objects, "__Vrrp__", GUINT_TO_POINTER(vrrp));
g_free(path);
if (local_error != NULL) {
log_message(LOG_INFO, "Registering VRRP object on %s failed: %s",
path, local_error->message);
g_clear_error(&local_error);
}
/* for each available VRRP instance, register an object */
if (LIST_ISEMPTY(vrrp_data->vrrp))
return;
for (e = LIST_HEAD(vrrp_data->vrrp); e; ELEMENT_NEXT(e)) {
vrrp_t * vrrp = ELEMENT_DATA(e);
dbus_create_object(vrrp);
}
/* Send a signal to say we have started */
path = dbus_object_create_path_vrrp();
dbus_emit_signal(global_connection, path, DBUS_VRRP_INTERFACE, "VrrpStarted", NULL);
g_free(path);
/* Notify DBus of the state of our instances */
for (e = LIST_HEAD(vrrp_data->vrrp); e; ELEMENT_NEXT(e)) {
vrrp_t * vrrp = ELEMENT_DATA(e);
dbus_send_state_signal(vrrp);
}
}
/* run if bus name is acquired successfully */
static void
on_name_acquired(__attribute__((unused)) GDBusConnection *connection,
const gchar *name,
__attribute__((unused)) gpointer user_data)
{
log_message(LOG_INFO, "Acquired the name %s on the session bus", name);
}
/* run if bus name or connection are lost */
static void
on_name_lost(GDBusConnection *connection,
const gchar *name,
__attribute__((unused)) gpointer user_data)
{
log_message(LOG_INFO, "Lost the name %s on the session bus", name);
global_connection = connection;
g_hash_table_foreach_remove(objects, unregister_object, NULL);
objects = NULL;
global_connection = NULL;
}
static gchar*
read_file(gchar* filepath)
{
FILE * f;
size_t length;
gchar *ret = NULL;
f = fopen(filepath, "r");
if (f) {
fseek(f, 0, SEEK_END);
length = (size_t)ftell(f);
fseek(f, 0, SEEK_SET);
/* We can't use MALLOC since it isn't thread safe */
ret = MALLOC(length + 1);
if (ret) {
if (fread(ret, length, 1, f) != 1) {
log_message(LOG_INFO, "Failed to read all of %s", filepath);
}
ret[length] = '\0';
}
else
log_message(LOG_INFO, "Unable to read Dbus file %s", filepath);
fclose(f);
}
return ret;
}
static void *
dbus_main(__attribute__ ((unused)) void *unused)
{
gchar *introspection_xml;
guint owner_id;
const char *service_name;
objects = g_hash_table_new(g_str_hash, g_str_equal);
/* DBus service org.keepalived.Vrrp1 exposes two interfaces, Vrrp and Instance.
* Vrrp is implemented by a single VRRP object for general purposes, such as printing
* data or signaling that the VRRP process has been stopped.
* Instance is implemented by an Instance object for every VRRP Instance in vrrp_data.
* It exposes instance specific methods and properties.
*/
#ifdef DBUS_NEED_G_TYPE_INIT
g_type_init();
#endif
GError *error = NULL;
/* read service interface data from xml files */
introspection_xml = read_file(DBUS_VRRP_INTERFACE_FILE_PATH);
if (!introspection_xml)
return NULL;
vrrp_introspection_data = g_dbus_node_info_new_for_xml(introspection_xml, &error);
FREE(introspection_xml);
if (error != NULL) {
log_message(LOG_INFO, "Parsing DBus interface %s from file %s failed: %s",
DBUS_VRRP_INTERFACE, DBUS_VRRP_INTERFACE_FILE_PATH, error->message);
g_clear_error(&error);
return NULL;
}
introspection_xml = read_file(DBUS_VRRP_INSTANCE_INTERFACE_FILE_PATH);
if (!introspection_xml)
return NULL;
vrrp_instance_introspection_data = g_dbus_node_info_new_for_xml(introspection_xml, &error);
FREE(introspection_xml);
if (error != NULL) {
log_message(LOG_INFO, "Parsing DBus interface %s from file %s failed: %s",
DBUS_VRRP_INSTANCE_INTERFACE, DBUS_VRRP_INSTANCE_INTERFACE_FILE_PATH, error->message);
g_clear_error(&error);
return NULL;
}
service_name = global_data->dbus_service_name ? global_data->dbus_service_name : DBUS_SERVICE_NAME;
owner_id = g_bus_own_name(G_BUS_TYPE_SYSTEM,
service_name,
G_BUS_NAME_OWNER_FLAGS_NONE,
on_bus_acquired,
on_name_acquired,
on_name_lost,
NULL, /* user_data */
NULL); /* user_data_free_func */
loop = g_main_loop_new(NULL, FALSE);
g_main_loop_run(loop);
/* cleanup after loop terminates */
g_main_loop_unref(loop);
g_bus_unown_name(owner_id);
global_connection = NULL;
sem_post(&thread_end);
pthread_exit(0);
}
/* The following functions are run in the context of the main vrrp thread */
/* send signal VrrpStatusChange
* containing the new state of vrrp */
void
dbus_send_state_signal(vrrp_t *vrrp)
{
gchar *object_path;
GVariant *args;
/* the interface will go through the initial state changes before
* the main loop can be started and global_connection initialised */
if (global_connection == NULL)
return;
object_path = dbus_object_create_path_instance(IF_NAME(IF_BASE_IFP(vrrp->ifp)), vrrp->vrid, vrrp->family);
args = g_variant_new("(u)", vrrp->state);
dbus_emit_signal(global_connection, object_path, DBUS_VRRP_INSTANCE_INTERFACE, "VrrpStatusChange", args);
g_free(object_path);
}
/* send signal VrrpRestarted */
static void
dbus_send_reload_signal(void)
{
gchar *path;
if (global_connection == NULL)
return;
path = dbus_object_create_path_vrrp();
dbus_emit_signal(global_connection, path, DBUS_VRRP_INTERFACE, "VrrpReloaded", NULL);
g_free(path);
}
static gboolean
dbus_unregister_object(char *str)
{
gboolean ret = false;
gpointer value = g_hash_table_lookup(objects, str);
if (value) {
ret = unregister_object(str, value, NULL);
log_message(LOG_INFO, "Deleted DBus object for instance %s", str);
}
#ifdef DBUS_DEBUG
else
log_message(LOG_INFO, "DBus object not found for instance %s", str);
#endif
return ret;
}
void
dbus_remove_object(vrrp_t *vrrp)
{
dbus_unregister_object(vrrp->iname);
}
static int
handle_dbus_msg(__attribute__((unused)) thread_t *thread)
{
dbus_queue_ent_t *ent;
char recv_buf;
vrrp_t *vrrp;
#ifdef _WITH_DBUS_CREATE_INSTANCE_
gchar *name;
#endif
if (read(dbus_in_pipe[0], &recv_buf, 1) != 1)
log_message(LOG_INFO, "Read from DBus thread to vrrp thread failed");
if ((ent = ent_ptr) != NULL) {
ent->reply = DBUS_SUCCESS;
if (ent->action == DBUS_PRINT_DATA) {
log_message(LOG_INFO, "Printing VRRP data on DBus request");
vrrp_print_data();
}
else if (ent->action == DBUS_PRINT_STATS) {
log_message(LOG_INFO, "Printing VRRP stats on DBus request");
vrrp_print_stats();
}
#ifdef _WITH_DBUS_CREATE_INSTANCE_
else if (ent->action == DBUS_CREATE_INSTANCE) {
g_variant_get(ent->args, "(s)", &name);
ent->reply = dbus_create_object_params(name, ent->ifname, ent->vrid, ent->family, true);
}
else if (ent->action == DBUS_DESTROY_INSTANCE) {
g_variant_get(ent->args, "(s)", &name);
if (!dbus_unregister_object(name))
ent->reply = DBUS_INSTANCE_NOT_FOUND;
}
#endif
else if (ent->action == DBUS_SEND_GARP) {
ent->reply = DBUS_INTERFACE_NOT_FOUND;
vrrp = get_vrrp_instance(ent->ifname, ent->vrid, ent->family);
if (vrrp) {
log_message(LOG_INFO, "Sending garps on %s on DBus request", vrrp->iname);
vrrp_send_link_update(vrrp, 1);
ent->reply = DBUS_SUCCESS;
}
}
else if (ent->action == DBUS_GET_NAME ||
ent->action == DBUS_GET_STATUS) {
/* we look for the vrrp instance object that corresponds to our interface and group */
ent->reply = DBUS_INTERFACE_NOT_FOUND;
vrrp = get_vrrp_instance(ent->ifname, ent->vrid, ent->family);
if (vrrp) {
/* the property_name argument is the property we want to Get */
if (ent->action == DBUS_GET_NAME)
ent->args = g_variant_new("(s)", vrrp->iname);
else if (ent->action == DBUS_GET_STATUS)
ent->args = g_variant_new("(us)", vrrp->state, state_str(vrrp->state));
else
ent->args = NULL; /* How did we get here? */
ent->reply = DBUS_SUCCESS;
}
}
if (write(dbus_out_pipe[1], ent, 1) != 1)
log_message(LOG_INFO, "Write from main thread to DBus thread failed");
}
thread_add_read(master, handle_dbus_msg, NULL, dbus_in_pipe[0], TIMER_NEVER);
return 0;
}
void
dbus_reload(list o, list n)
{
element e1, e2, e3;
vrrp_t *vrrp_n, *vrrp_o, *vrrp_n3;
if (!LIST_ISEMPTY(n)) {
for (e1 = LIST_HEAD(n); e1; ELEMENT_NEXT(e1)) {
char *n_name;
bool match_found;
vrrp_n = ELEMENT_DATA(e1);
if (LIST_ISEMPTY(o)) {
dbus_create_object(vrrp_n);
continue;
}
n_name = IF_BASE_IFP(vrrp_n->ifp)->ifname;
/* Try an find an instance with same vrid/family/interface that existed before and now */
for (e2 = LIST_HEAD(o), match_found = false; e2 && !match_found; ELEMENT_NEXT(e2)) {
vrrp_o = ELEMENT_DATA(e2);
if (vrrp_n->vrid == vrrp_o->vrid &&
vrrp_n->family == vrrp_o->family &&
!strcmp(n_name, IF_BASE_IFP(vrrp_o->ifp)->ifname)) {
/* If the old instance exists in the new config,
* then the dbus object will exist */
if (!strcmp(vrrp_n->iname, vrrp_o->iname)) {
match_found = true;
break;
}
/* Check if the old instance name we found still exists
* (but has a different vrid/family/interface) */
for (e3 = LIST_HEAD(n); e3; ELEMENT_NEXT(e3)) {
vrrp_n3 = ELEMENT_DATA(e3);
if (!strcmp(vrrp_o->iname, vrrp_n3->iname)) {
match_found = true;
break;
}
}
}
}
if (match_found)
continue;
dbus_create_object(vrrp_n);
}
}
/* Signal we have reloaded */
dbus_send_reload_signal();
/* We need to reinstate the read thread */
thread_add_read(master, handle_dbus_msg, NULL, dbus_in_pipe[0], TIMER_NEVER);
}
bool
dbus_start(void)
{
pthread_t dbus_thread;
sigset_t sigset, cursigset;
if (open_pipe(dbus_in_pipe)) {
log_message(LOG_INFO, "Unable to create inbound dbus pipe - disabling DBus");
return false;
}
if (open_pipe(dbus_out_pipe)) {
log_message(LOG_INFO, "Unable to create outbound dbus pipe - disabling DBus");
close(dbus_in_pipe[0]);
close(dbus_in_pipe[1]);
return false;
}
/* We don't want the main thread to block when using the pipes,
* but the other side of the pipes should block. */
fcntl(dbus_in_pipe[1], F_SETFL, fcntl(dbus_in_pipe[1], F_GETFL) & ~O_NONBLOCK);
fcntl(dbus_out_pipe[0], F_SETFL, fcntl(dbus_out_pipe[0], F_GETFL) & ~O_NONBLOCK);
thread_add_read(master, handle_dbus_msg, NULL, dbus_in_pipe[0], TIMER_NEVER);
/* Initialise the thread termination semaphore */
sem_init(&thread_end, 0, 0);
/* Block signals (all) we don't want the new thread to process */
sigfillset(&sigset);
pthread_sigmask(SIG_SETMASK, &sigset, &cursigset);
/* Now create the dbus thread */
pthread_create(&dbus_thread, NULL, &dbus_main, NULL);
/* Reenable our signals */
pthread_sigmask(SIG_SETMASK, &cursigset, NULL);
return true;
}
void
dbus_stop(void)
{
struct timespec thread_end_wait;
int ret;
gchar *path;
if (global_connection != NULL) {
path = dbus_object_create_path_vrrp();
dbus_emit_signal(global_connection, path, DBUS_VRRP_INTERFACE, "VrrpStopped", NULL);
g_free(path);
}
g_main_loop_quit(loop);
g_dbus_node_info_unref(vrrp_introspection_data);
g_dbus_node_info_unref(vrrp_instance_introspection_data);
clock_gettime(CLOCK_REALTIME, &thread_end_wait);
thread_end_wait.tv_sec += 1;
while ((ret = sem_timedwait(&thread_end, &thread_end_wait)) == -1 && errno == EINTR) ;
if (ret == -1 ) {
if (errno == ETIMEDOUT)
log_message(LOG_INFO, "DBus thread termination timed out");
else
log_message(LOG_INFO, "sem_timewait error %d", errno);
}
else {
log_message(LOG_INFO, "Released DBus");
sem_destroy(&thread_end);
}
}
#ifdef THREAD_DUMP
void
register_vrrp_dbus_addresses(void)
{
register_thread_address("handle_dbus_msg", handle_dbus_msg);
}
#endif