diff options
author | Stephen Gallagher <sgallagh@redhat.com> | 2010-02-18 07:49:04 -0500 |
---|---|---|
committer | Stephen Gallagher <sgallagh@redhat.com> | 2010-02-18 13:48:45 -0500 |
commit | 1c48b5a62f73234ed26bb20f0ab345ab61cda0ab (patch) | |
tree | 0b6cddd567a862e1a7b5df23764869782a62ca78 /src/sbus/sssd_dbus_connection.c | |
parent | 8c56df3176f528fe0260974b3bf934173c4651ea (diff) | |
download | sssd-1c48b5a62f73234ed26bb20f0ab345ab61cda0ab.tar.gz sssd-1c48b5a62f73234ed26bb20f0ab345ab61cda0ab.tar.xz sssd-1c48b5a62f73234ed26bb20f0ab345ab61cda0ab.zip |
Rename server/ directory to src/
Also update BUILD.txt
Diffstat (limited to 'src/sbus/sssd_dbus_connection.c')
-rw-r--r-- | src/sbus/sssd_dbus_connection.c | 692 |
1 files changed, 692 insertions, 0 deletions
diff --git a/src/sbus/sssd_dbus_connection.c b/src/sbus/sssd_dbus_connection.c new file mode 100644 index 000000000..38ccc6ab1 --- /dev/null +++ b/src/sbus/sssd_dbus_connection.c @@ -0,0 +1,692 @@ +/* + Authors: + Simo Sorce <ssorce@redhat.com> + Stephen Gallagher <sgallagh@redhat.com> + + Copyright (C) 2009 Red Hat + + 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 3 of the License, or + (at your option) any later version. + + 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. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <sys/time.h> +#include "util/util.h" +#include "dbus/dbus.h" +#include "sbus/sssd_dbus.h" +#include "sbus/sssd_dbus_private.h" + +/* Types */ +struct dbus_ctx_list; + +struct sbus_interface_p { + struct sbus_interface_p *prev, *next; + struct sbus_connection *conn; + struct sbus_interface *intf; +}; + +static bool path_in_interface_list(struct sbus_interface_p *list, + const char *path); +static void sbus_unreg_object_paths(struct sbus_connection *conn); + +static int sbus_auto_reconnect(struct sbus_connection *conn); + +static void sbus_dispatch(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval tv, void *data) +{ + struct tevent_timer *new_event; + struct sbus_connection *conn; + DBusConnection *dbus_conn; + int ret; + + if (data == NULL) return; + + conn = talloc_get_type(data, struct sbus_connection); + + dbus_conn = conn->dbus.conn; + DEBUG(9, ("dbus conn: %lX\n", dbus_conn)); + + if (conn->retries > 0) { + DEBUG(6, ("SBUS is reconnecting. Deferring.\n")); + /* Currently trying to reconnect, defer dispatch */ + new_event = tevent_add_timer(ev, conn, tv, sbus_dispatch, conn); + if (new_event == NULL) { + DEBUG(0,("Could not defer dispatch!\n")); + } + return; + } + + if ((!dbus_connection_get_is_connected(dbus_conn)) && + (conn->max_retries != 0)) { + /* Attempt to reconnect automatically */ + ret = sbus_auto_reconnect(conn); + if (ret == EOK) { + DEBUG(1, ("Performing auto-reconnect\n")); + return; + } + + DEBUG(0, ("Cannot start auto-reconnection.\n")); + conn->reconnect_callback(conn, + SBUS_RECONNECT_ERROR, + conn->reconnect_pvt); + return; + } + + if ((conn->disconnect) || + (!dbus_connection_get_is_connected(dbus_conn))) { + DEBUG(3,("Connection is not open for dispatching.\n")); + /* + * Free the connection object. + * This will invoke the destructor for the connection + */ + talloc_free(conn); + conn = NULL; + return; + } + + /* Dispatch only once each time through the mainloop to avoid + * starving other features + */ + ret = dbus_connection_get_dispatch_status(dbus_conn); + if (ret != DBUS_DISPATCH_COMPLETE) { + DEBUG(9,("Dispatching.\n")); + dbus_connection_dispatch(dbus_conn); + } + + /* If other dispatches are waiting, queue up the dispatch function + * for the next loop. + */ + ret = dbus_connection_get_dispatch_status(dbus_conn); + if (ret != DBUS_DISPATCH_COMPLETE) { + new_event = tevent_add_timer(ev, conn, tv, sbus_dispatch, conn); + if (new_event == NULL) { + DEBUG(2,("Could not add dispatch event!\n")); + + /* TODO: Calling exit here is bad */ + exit(1); + } + } +} + +/* dbus_connection_wakeup_main + * D-BUS makes a callback to the wakeup_main function when + * it has data available for dispatching. + * In order to avoid blocking, this function will create a now() + * timed event to perform the dispatch during the next iteration + * through the mainloop + */ +static void sbus_conn_wakeup_main(void *data) +{ + struct sbus_connection *conn; + struct timeval tv; + struct tevent_timer *te; + + conn = talloc_get_type(data, struct sbus_connection); + + tv = tevent_timeval_current(); + + /* D-BUS calls this function when it is time to do a dispatch */ + te = tevent_add_timer(conn->ev, conn, tv, sbus_dispatch, conn); + if (te == NULL) { + DEBUG(2,("Could not add dispatch event!\n")); + /* TODO: Calling exit here is bad */ + exit(1); + } +} + +static int sbus_conn_set_fns(struct sbus_connection *conn); + +/* + * integrate_connection_with_event_loop + * Set up a D-BUS connection to use the libevents mainloop + * for handling file descriptor and timed events + */ +int sbus_init_connection(TALLOC_CTX *ctx, + struct tevent_context *ev, + DBusConnection *dbus_conn, + struct sbus_interface *intf, + int connection_type, + struct sbus_connection **_conn) +{ + struct sbus_connection *conn; + int ret; + + DEBUG(5,("Adding connection %lX\n", dbus_conn)); + conn = talloc_zero(ctx, struct sbus_connection); + + conn->ev = ev; + conn->type = SBUS_CONNECTION; + conn->dbus.conn = dbus_conn; + conn->connection_type = connection_type; + + ret = sbus_conn_add_interface(conn, intf); + if (ret != EOK) { + talloc_free(conn); + return ret; + } + + ret = sbus_conn_set_fns(conn); + if (ret != EOK) { + talloc_free(conn); + return ret; + } + + *_conn = conn; + return ret; +} + +static int sbus_conn_set_fns(struct sbus_connection *conn) +{ + dbus_bool_t dbret; + + /* + * Set the default destructor + * Connections can override this with + * sbus_conn_set_destructor + */ + sbus_conn_set_destructor(conn, NULL); + + /* Set up DBusWatch functions */ + dbret = dbus_connection_set_watch_functions(conn->dbus.conn, + sbus_add_watch, + sbus_remove_watch, + sbus_toggle_watch, + conn, NULL); + if (!dbret) { + DEBUG(2,("Error setting up D-BUS connection watch functions\n")); + return EIO; + } + + /* Set up DBusTimeout functions */ + dbret = dbus_connection_set_timeout_functions(conn->dbus.conn, + sbus_add_timeout, + sbus_remove_timeout, + sbus_toggle_timeout, + conn, NULL); + if (!dbret) { + DEBUG(2,("Error setting up D-BUS server timeout functions\n")); + /* FIXME: free resources ? */ + return EIO; + } + + /* Set up dispatch handler */ + dbus_connection_set_wakeup_main_function(conn->dbus.conn, + sbus_conn_wakeup_main, + conn, NULL); + + /* Set up any method_contexts passed in */ + + /* Attempt to dispatch immediately in case of opportunistic + * services connecting before the handlers were all up. + * If there are no messages to be dispatched, this will do + * nothing. + */ + sbus_conn_wakeup_main(conn); + + return EOK; +} + +int sbus_new_connection(TALLOC_CTX *ctx, struct tevent_context *ev, + const char *address, struct sbus_interface *intf, + struct sbus_connection **_conn) +{ + struct sbus_connection *conn; + DBusConnection *dbus_conn; + DBusError dbus_error; + int ret; + + dbus_error_init(&dbus_error); + + /* Open a shared D-BUS connection to the address */ + dbus_conn = dbus_connection_open(address, &dbus_error); + if (!dbus_conn) { + DEBUG(1, ("Failed to open connection: name=%s, message=%s\n", + dbus_error.name, dbus_error.message)); + if (dbus_error_is_set(&dbus_error)) dbus_error_free(&dbus_error); + return EIO; + } + + ret = sbus_init_connection(ctx, ev, dbus_conn, intf, + SBUS_CONN_TYPE_SHARED, &conn); + if (ret != EOK) { + /* FIXME: release resources */ + } + + /* Store the address for later reconnection */ + conn->address = talloc_strdup(conn, address); + + dbus_connection_set_exit_on_disconnect(conn->dbus.conn, FALSE); + + *_conn = conn; + return ret; +} + +/* + * sbus_conn_set_destructor + * Configures a callback to clean up this connection when it + * is finalized. + * @param conn The sbus_connection created + * when this connection was established + * @param destructor The destructor function that should be + * called when the connection is finalized. If passed NULL, + * this will reset the connection to the default destructor. + */ +void sbus_conn_set_destructor(struct sbus_connection *conn, + sbus_conn_destructor_fn destructor) +{ + if (!conn) return; + + conn->destructor = destructor; + /* TODO: Should we try to handle the talloc_destructor too? */ +} + +int sbus_default_connection_destructor(void *ctx) +{ + struct sbus_connection *conn; + conn = talloc_get_type(ctx, struct sbus_connection); + + DEBUG(5, ("Invoking default destructor on connection %lX\n", + conn->dbus.conn)); + if (conn->connection_type == SBUS_CONN_TYPE_PRIVATE) { + /* Private connections must be closed explicitly */ + dbus_connection_close(conn->dbus.conn); + } + else if (conn->connection_type == SBUS_CONN_TYPE_SHARED) { + /* Shared connections are destroyed when their last reference is removed */ + } + else { + /* Critical Error! */ + DEBUG(1,("Critical Error, connection_type is neither shared nor private!\n")); + return -1; + } + + /* Remove object path */ + /* TODO: Remove object paths */ + + dbus_connection_unref(conn->dbus.conn); + return 0; +} + +/* + * sbus_get_connection + * Utility function to retreive the DBusConnection object + * from a sbus_connection + */ +DBusConnection *sbus_get_connection(struct sbus_connection *conn) +{ + return conn->dbus.conn; +} + +void sbus_disconnect (struct sbus_connection *conn) +{ + if (conn == NULL) { + return; + } + + DEBUG(5,("Disconnecting %lX\n", conn->dbus.conn)); + + /******************************* + * Referencing conn->dbus.conn */ + dbus_connection_ref(conn->dbus.conn); + + conn->disconnect = 1; + + /* Invoke the custom destructor, if it exists */ + if (conn->destructor) { + conn->destructor(conn); + } + + /* Unregister object paths */ + sbus_unreg_object_paths(conn); + + /* Disable watch functions */ + dbus_connection_set_watch_functions(conn->dbus.conn, + NULL, NULL, NULL, + NULL, NULL); + /* Disable timeout functions */ + dbus_connection_set_timeout_functions(conn->dbus.conn, + NULL, NULL, NULL, + NULL, NULL); + + /* Disable dispatch status function */ + dbus_connection_set_dispatch_status_function(conn->dbus.conn, + NULL, NULL, NULL); + + /* Disable wakeup main function */ + dbus_connection_set_wakeup_main_function(conn->dbus.conn, + NULL, NULL, NULL); + + /* Finalize the connection */ + sbus_default_connection_destructor(conn); + + dbus_connection_unref(conn->dbus.conn); + /* Unreferenced conn->dbus_conn * + ******************************/ + + DEBUG(5,("Disconnected %lX\n", conn->dbus.conn)); +} + +static int sbus_reply_internal_error(DBusMessage *message, + struct sbus_connection *conn) { + DBusMessage *reply = dbus_message_new_error(message, DBUS_ERROR_IO_ERROR, + "Internal Error"); + if (reply) { + sbus_conn_send_reply(conn, reply); + dbus_message_unref(reply); + return DBUS_HANDLER_RESULT_HANDLED; + } + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; +} + +/* messsage_handler + * Receive messages and process them + */ +DBusHandlerResult sbus_message_handler(DBusConnection *dbus_conn, + DBusMessage *message, + void *user_data) +{ + struct sbus_interface_p *intf_p; + const char *method; + const char *path; + const char *msg_interface; + DBusMessage *reply = NULL; + int i, ret; + int found; + + if (!user_data) { + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + } + intf_p = talloc_get_type(user_data, struct sbus_interface_p); + + method = dbus_message_get_member(message); + DEBUG(9, ("Received SBUS method [%s]\n", method)); + 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; + + /* Validate the D-BUS path */ + if (strcmp(path, intf_p->intf->path) != 0) + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + + /* Validate the method interface */ + if (strcmp(msg_interface, intf_p->intf->interface) == 0) { + found = 0; + for (i = 0; intf_p->intf->methods[i].method != NULL; i++) { + if (strcmp(method, intf_p->intf->methods[i].method) == 0) { + found = 1; + ret = intf_p->intf->methods[i].fn(message, intf_p->conn); + if (ret != EOK) { + return sbus_reply_internal_error(message, intf_p->conn); + } + break; + } + } + + if (!found) { + /* Reply DBUS_ERROR_UNKNOWN_METHOD */ + DEBUG(1, ("No matching method found for %s.\n", method)); + reply = dbus_message_new_error(message, DBUS_ERROR_UNKNOWN_METHOD, NULL); + sbus_conn_send_reply(intf_p->conn, reply); + dbus_message_unref(reply); + } + } + else { + /* Special case: check for Introspection request + * This is usually only useful for system bus connections + */ + if (strcmp(msg_interface, DBUS_INTROSPECT_INTERFACE) == 0 && + strcmp(method, DBUS_INTROSPECT_METHOD) == 0) + { + if (intf_p->intf->introspect_fn) { + /* If we have been asked for introspection data and we have + * an introspection function registered, user that. + */ + ret = intf_p->intf->introspect_fn(message, intf_p->conn); + if (ret != EOK) { + return sbus_reply_internal_error(message, intf_p->conn); + } + } + } + else + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + } + + return DBUS_HANDLER_RESULT_HANDLED; +} + +/* Adds a new D-BUS path message handler to the connection + * Note: this must be a unique path. + */ +int sbus_conn_add_interface(struct sbus_connection *conn, + struct sbus_interface *intf) +{ + struct sbus_interface_p *intf_p; + dbus_bool_t dbret; + const char *path; + + if (!conn || !intf || !intf->vtable.message_function) { + return EINVAL; + } + + path = intf->path; + + if (path_in_interface_list(conn->intf_list, path)) { + DEBUG(0, ("Cannot add method context with identical path.\n")); + return EINVAL; + } + + intf_p = talloc_zero(conn, struct sbus_interface_p); + if (!intf_p) { + return ENOMEM; + } + intf_p->conn = conn; + intf_p->intf = intf; + + DLIST_ADD(conn->intf_list, intf_p); + + dbret = dbus_connection_register_object_path(conn->dbus.conn, + path, &intf->vtable, intf_p); + if (!dbret) { + DEBUG(0, ("Could not register object path to the connection.\n")); + return ENOMEM; + } + + return EOK; +} + +static bool path_in_interface_list(struct sbus_interface_p *list, + const char *path) +{ + struct sbus_interface_p *iter; + + if (!list || !path) { + return false; + } + + iter = list; + while (iter != NULL) { + if (strcmp(iter->intf->path, path) == 0) { + return true; + } + iter = iter->next; + } + + return false; +} + +static void sbus_unreg_object_paths(struct sbus_connection *conn) +{ + struct sbus_interface_p *iter = conn->intf_list; + + while (iter != NULL) { + dbus_connection_unregister_object_path(conn->dbus.conn, + iter->intf->path); + iter = iter->next; + } +} + +void sbus_conn_set_private_data(struct sbus_connection *conn, void *pvt_data) +{ + conn->pvt_data = pvt_data; +} + +void *sbus_conn_get_private_data(struct sbus_connection *conn) +{ + return conn->pvt_data; +} + +static void sbus_reconnect(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval tv, void *data) +{ + struct sbus_connection *conn; + struct sbus_interface_p *iter; + DBusError dbus_error; + dbus_bool_t dbret; + int ret; + + conn = talloc_get_type(data, struct sbus_connection); + dbus_error_init(&dbus_error); + + DEBUG(3, ("Making reconnection attempt %d to [%s]\n", + conn->retries, conn->address)); + conn->dbus.conn = dbus_connection_open(conn->address, &dbus_error); + if (conn->dbus.conn) { + /* We successfully reconnected. Set up mainloop integration. */ + DEBUG(3, ("Reconnected to [%s]\n", conn->address)); + ret = sbus_conn_set_fns(conn); + if (ret != EOK) { + dbus_connection_unref(conn->dbus.conn); + goto failed; + } + + /* Re-register object paths */ + iter = conn->intf_list; + while (iter) { + dbret = dbus_connection_register_object_path(conn->dbus.conn, + iter->intf->path, + &iter->intf->vtable, + iter); + if (!dbret) { + DEBUG(0, ("Could not register object path.\n")); + dbus_connection_unref(conn->dbus.conn); + goto failed; + } + iter = iter->next; + } + + /* Reset retries to 0 to resume dispatch processing */ + conn->retries = 0; + + /* Notify the owner of this connection that the + * reconnection was successful + */ + conn->reconnect_callback(conn, + SBUS_RECONNECT_SUCCESS, + conn->reconnect_pvt); + return; + } + +failed: + /* Reconnection failed, try again in a few seconds */ + DEBUG(1, ("Failed to open connection: name=%s, message=%s\n", + dbus_error.name, dbus_error.message)); + if (dbus_error_is_set(&dbus_error)) dbus_error_free(&dbus_error); + + conn->retries++; + + /* Check if we've passed our last chance or if we've lost track of + * our retry count somehow + */ + if ((conn->retries > conn->max_retries) || (conn->retries <= 0)) { + conn->reconnect_callback(conn, + SBUS_RECONNECT_EXCEEDED_RETRIES, + conn->reconnect_pvt); + } + + if (conn->retries == 2) { + /* Wait 3 seconds before the second reconnect attempt */ + tv.tv_sec += 3; + } + else if (conn->retries == 3) { + /* Wait 10 seconds before the third reconnect attempt */ + tv.tv_sec += 10; + } + else { + /* Wait 30 seconds before all subsequent reconnect attempts */ + tv.tv_sec += 30; + } + + te = tevent_add_timer(conn->ev, conn, tv, sbus_reconnect, conn); + if (!te) { + conn->reconnect_callback(conn, + SBUS_RECONNECT_ERROR, + conn->reconnect_pvt); + } +} + +/* This function will free and recreate the sbus_connection, + * calling functions need to be aware of this (and whether + * they have attached a talloc destructor to the + * sbus_connection. + */ +static int sbus_auto_reconnect(struct sbus_connection *conn) +{ + struct tevent_timer *te = NULL; + struct timeval tv; + + conn->retries++; + if (conn->retries >= conn->max_retries) { + /* Return EIO (to tell the calling process it + * needs to create a new connection from scratch + */ + return EIO; + } + + gettimeofday(&tv, NULL); + tv.tv_sec += 1; /* Wait 1 second before the first reconnect attempt */ + te = tevent_add_timer(conn->ev, conn, tv, sbus_reconnect, conn); + if (!te) { + return EIO; + } + + return EOK; +} + +/* Max retries */ +void sbus_reconnect_init(struct sbus_connection *conn, + int max_retries, + sbus_conn_reconn_callback_fn callback, + void *pvt) +{ + if (max_retries < 0 || callback == NULL) return; + + conn->retries = 0; + conn->max_retries = max_retries; + conn->reconnect_callback = callback; + conn->reconnect_pvt = pvt; +} + +bool sbus_conn_disconnecting(struct sbus_connection *conn) +{ + if (conn->disconnect == 1) return true; + return false; +} + +void sbus_conn_send_reply(struct sbus_connection *conn, DBusMessage *reply) +{ + dbus_connection_send(conn->dbus.conn, reply, NULL); +} + |