From b8b206c6e84757c6d4d36cec619a1e7c37d1eb5f Mon Sep 17 00:00:00 2001 From: Robert Tari Date: Wed, 10 Feb 2021 13:06:05 +0100 Subject: Move source files to src --- src/indicator-service-manager.c | 704 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 704 insertions(+) create mode 100644 src/indicator-service-manager.c (limited to 'src/indicator-service-manager.c') diff --git a/src/indicator-service-manager.c b/src/indicator-service-manager.c new file mode 100644 index 0000000..33bcba9 --- /dev/null +++ b/src/indicator-service-manager.c @@ -0,0 +1,704 @@ +/* +An object used to manage services. Either start them or +just connect to them. + +Copyright 2009 Canonical Ltd. + +Authors: + Ted Gould + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +version 3.0 as published by the Free Software Foundation. + +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 General Public License version 3.0 for more details. + +You should have received a copy of the GNU General Public +License along with this library. If not, see +. +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include + +#include "indicator-service-manager.h" +#include "gen-indicator-service.xml.h" +#include "dbus-shared.h" + +/* Private Stuff */ +/** + IndicatorServiceManagerPrivate: + @name: The well known dbus name the service should be on. + @service_proxy: The proxy to the service itself. + @connected: Whether we're connected to the service or not. + @this_service_version: The version of the service that we're looking for. + @restart_count: The number of times we've restarted this service. +*/ +typedef struct _IndicatorServiceManagerPrivate IndicatorServiceManagerPrivate; +struct _IndicatorServiceManagerPrivate { + gchar * name; + GDBusProxy * service_proxy; + GCancellable * service_proxy_cancel; + guint name_watcher; + gboolean connected; + guint this_service_version; + guint restart_count; + gint restart_source; + GCancellable * watch_cancel; +}; + +/* Signals Stuff */ +enum { + CONNECTION_CHANGE, + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL] = { 0 }; + +/* If this env variable is set, we don't restart */ +#define TIMEOUT_ENV_NAME "INDICATOR_SERVICE_RESTART_DISABLE" +#define TIMEOUT_MULTIPLIER 100 /* In ms */ +/* What to reset the restart_count to if we know that we're + in a recoverable error condition, but waiting a little bit + will probably make things better. 5 ~= 3 sec. */ +#define TIMEOUT_A_LITTLE_WHILE 5 + +/* Properties */ +/* Enum for the properties so that they can be quickly + found and looked up. */ +enum { + PROP_0, + PROP_NAME, + PROP_VERSION +}; + +/* The strings so that they can be slowly looked up. */ +#define PROP_NAME_S "name" +#define PROP_VERSION_S "version" + +/* GDBus Stuff */ +static GDBusNodeInfo * node_info = NULL; +static GDBusInterfaceInfo * interface_info = NULL; + +static void indicator_service_manager_class_init (IndicatorServiceManagerClass *klass); +static void indicator_service_manager_init (IndicatorServiceManager *self); +static void indicator_service_manager_dispose (GObject *object); +static void indicator_service_manager_finalize (GObject *object); + +/* Prototypes */ +static void set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec); +static void get_property (GObject * object, guint prop_id, GValue * value, GParamSpec * pspec); +static void start_service (IndicatorServiceManager * service); +static void start_service_again (IndicatorServiceManager * manager); +static void unwatch (GDBusProxy * proxy); +static void service_proxy_cb (GObject * object, GAsyncResult * res, gpointer user_data); +static void service_proxy_name_changed (GDBusConnection * connection, const gchar * sender_name, const gchar * object_path, const gchar * interface_name, const gchar * signal_name, GVariant * parameters, gpointer user_data); + +G_DEFINE_TYPE_WITH_PRIVATE (IndicatorServiceManager, indicator_service_manager, G_TYPE_OBJECT); + +/* Build all of our signals and proxies and tie everything + all together. Lovely. */ +static void +indicator_service_manager_class_init (IndicatorServiceManagerClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->dispose = indicator_service_manager_dispose; + object_class->finalize = indicator_service_manager_finalize; + + /* Property funcs */ + object_class->set_property = set_property; + object_class->get_property = get_property; + + /** + IndicatorServiceManager::connecton-change: + @arg0: The #IndicatorServiceManager object + @arg1: The state of the connection, TRUE is connected. + + Signaled when the service is connected or disconnected + depending on it's previous state. + */ + signals[CONNECTION_CHANGE] = g_signal_new (INDICATOR_SERVICE_MANAGER_SIGNAL_CONNECTION_CHANGE, + G_TYPE_FROM_CLASS(klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (IndicatorServiceManagerClass, connection_change), + NULL, NULL, + g_cclosure_marshal_VOID__BOOLEAN, + G_TYPE_NONE, 1, G_TYPE_BOOLEAN, G_TYPE_NONE); + + /* Properties */ + g_object_class_install_property(object_class, PROP_NAME, + g_param_spec_string(PROP_NAME_S, + "The DBus name for the service to monitor", + "This is the name that should be used to start a service.", + NULL, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property(object_class, PROP_VERSION, + g_param_spec_uint(PROP_VERSION_S, + "The version of the service that we're expecting.", + "A number to check and reject a service if it gives us the wrong number. This should match across the manager and the service", + 0, G_MAXUINT, 0, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + /* Setting up the DBus interfaces */ + if (node_info == NULL) { + GError * error = NULL; + + node_info = g_dbus_node_info_new_for_xml(_indicator_service, &error); + if (error != NULL) { + g_error("Unable to parse Indicator Service Interface description: %s", error->message); + g_error_free(error); + } + } + + if (interface_info == NULL) { + interface_info = g_dbus_node_info_lookup_interface(node_info, INDICATOR_SERVICE_INTERFACE); + + if (interface_info == NULL) { + g_error("Unable to find interface '" INDICATOR_SERVICE_INTERFACE "'"); + } + } + + return; +} + +/* This inits all the variable and sets up the proxy + to dbus. It doesn't look for the service as at this + point we don't know it's name. */ +static void +indicator_service_manager_init (IndicatorServiceManager *self) +{ + IndicatorServiceManagerPrivate * priv = indicator_service_manager_get_instance_private(self); + + /* Get the private variables in a decent state */ + priv->name = NULL; + priv->service_proxy = NULL; + priv->service_proxy_cancel = NULL; + priv->name_watcher = 0; + priv->connected = FALSE; + priv->this_service_version = 0; + priv->restart_count = 0; + priv->restart_source = 0; + priv->watch_cancel = NULL; + + return; +} + +/* If we're connected this provides all the signals to say + that we're about to not be. Then it takes down the proxies + and tells the service that we're not interested in being + its friend anymore either. */ +static void +indicator_service_manager_dispose (GObject *object) +{ + IndicatorServiceManager * sm = INDICATOR_SERVICE_MANAGER(object); + IndicatorServiceManagerPrivate * priv = indicator_service_manager_get_instance_private(sm); + + /* Removing the idle task to restart if it exists. */ + if (priv->restart_source != 0) { + g_source_remove(priv->restart_source); + } + /* Block any restart calls */ + priv->restart_source = -1; + + /* If we were connected we need to make sure to + tell people that it's no longer the case. */ + if (priv->connected) { + priv->connected = FALSE; + g_signal_emit(object, signals[CONNECTION_CHANGE], 0, FALSE, TRUE); + } + + if (priv->name_watcher != 0) { + g_dbus_connection_signal_unsubscribe(g_dbus_proxy_get_connection(priv->service_proxy), + priv->name_watcher); + priv->name_watcher = 0; + } + + /* If we're still getting the proxy, stop looking so we + can then clean up some more. */ + if (priv->service_proxy_cancel != NULL) { + g_cancellable_cancel(priv->service_proxy_cancel); + g_object_unref(priv->service_proxy_cancel); + priv->service_proxy_cancel = NULL; + } + + /* If we've sent a watch, cancel looking for the reply before + sending the unwatch */ + if (priv->watch_cancel != NULL) { + g_cancellable_cancel(priv->watch_cancel); + g_object_unref(priv->watch_cancel); + priv->watch_cancel = NULL; + } + + /* If we have a proxy, tell it we're shutting down. Just + to be polite about it. */ + if (priv->service_proxy != NULL) { + unwatch(priv->service_proxy); + } + + /* Destory our service proxy, we won't need it. */ + if (priv->service_proxy != NULL) { + g_object_unref(G_OBJECT(priv->service_proxy)); + priv->service_proxy = NULL; + } + + /* Let's see if our parents want to do anything. */ + G_OBJECT_CLASS (indicator_service_manager_parent_class)->dispose (object); + return; +} + +/* Ironically, we don't allocate a lot of memory ourselves. */ +static void +indicator_service_manager_finalize (GObject *object) +{ + IndicatorServiceManager * sm = INDICATOR_SERVICE_MANAGER(object); + IndicatorServiceManagerPrivate * priv = indicator_service_manager_get_instance_private(sm); + + if (priv->name != NULL) { + g_free(priv->name); + priv->name = NULL; + } + + G_OBJECT_CLASS (indicator_service_manager_parent_class)->finalize (object); + return; +} + +/* Either copies the name into the private variable or + sets the version. Do it wrong and it'll get upset. */ +static void +set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec) +{ + IndicatorServiceManager * self = INDICATOR_SERVICE_MANAGER(object); + g_return_if_fail(self != NULL); + + IndicatorServiceManagerPrivate * priv = indicator_service_manager_get_instance_private(self); + g_return_if_fail(priv != NULL); + + switch (prop_id) { + /* *********************** */ + case PROP_NAME: + if (priv->name != NULL) { + g_error("Name can not be set twice!"); + } + priv->name = g_value_dup_string(value); + start_service(self); + break; + /* *********************** */ + case PROP_VERSION: + priv->this_service_version = g_value_get_uint(value); + break; + /* *********************** */ + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } + + return; +} + +/* Grabs the values from the private variables and + puts them into the value. */ +static void +get_property (GObject * object, guint prop_id, GValue * value, GParamSpec * pspec) +{ + IndicatorServiceManager * self = INDICATOR_SERVICE_MANAGER(object); + g_return_if_fail(self != NULL); + + IndicatorServiceManagerPrivate * priv = indicator_service_manager_get_instance_private(self); + g_return_if_fail(priv != NULL); + + switch (prop_id) { + /* *********************** */ + case PROP_NAME: + g_value_set_string(value, priv->name); + break; + /* *********************** */ + case PROP_VERSION: + g_value_set_uint(value, priv->this_service_version); + break; + /* *********************** */ + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } + + return; +} + +/* Small little function to make a long function call a little + bit cleaner. */ +static void +unwatch (GDBusProxy * proxy) +{ + g_dbus_proxy_call(proxy, + "UnWatch", + NULL, /* parameters */ + G_DBUS_CALL_FLAGS_NONE, + -1, /* timeout */ + NULL, /* cancelable */ + NULL, /* callback */ + NULL); /* user data */ + return; +} + +/* A callback from telling a service that we want to watch + it. It gives us the service API version and the version + of the other APIs it supports. We check both of those. + If they don't match then we unwatch it. Otherwise, we + signal a connection change to tell the rest of the world + that we have a service now. */ +static void +watch_cb (GObject * object, GAsyncResult * res, gpointer user_data) +{ + GError * error = NULL; + IndicatorServiceManagerPrivate * priv = indicator_service_manager_get_instance_private(user_data); + + GVariant * params = g_dbus_proxy_call_finish(G_DBUS_PROXY(object), res, &error); + + if (error != NULL) { + g_warning("Unable to set watch on '%s': '%s'", priv->name, error->message); + g_error_free(error); + start_service_again(INDICATOR_SERVICE_MANAGER(user_data)); + return; + } + + guint service_api_version; + guint this_service_version; + + g_variant_get(params, "(uu)", &service_api_version, &this_service_version); + g_variant_unref(params); + + /* We've done it, now let's stop counting. */ + /* Note: we're not checking versions. Because, the hope is that + the guy holding the name we want with the wrong version will + drop and we can start another service quickly. */ + priv->restart_count = 0; + + if (service_api_version != INDICATOR_SERVICE_VERSION) { + g_warning("Service is using a different version of the service interface. Expecting %d and got %d.", INDICATOR_SERVICE_VERSION, service_api_version); + unwatch(priv->service_proxy); + + /* Let's make us wait a little while, then try again */ + priv->restart_count = TIMEOUT_A_LITTLE_WHILE; + start_service_again(INDICATOR_SERVICE_MANAGER(user_data)); + return; + } + + if (this_service_version != priv->this_service_version) { + g_warning("Service is using a different API version than the manager. Expecting %d and got %d.", priv->this_service_version, this_service_version); + unwatch(priv->service_proxy); + + /* Let's make us wait a little while, then try again */ + priv->restart_count = TIMEOUT_A_LITTLE_WHILE; + start_service_again(INDICATOR_SERVICE_MANAGER(user_data)); + return; + } + + if (!priv->connected) { + priv->connected = TRUE; + g_signal_emit(G_OBJECT(user_data), signals[CONNECTION_CHANGE], 0, TRUE, TRUE); + } + + return; +} + +/* The function that handles getting us connected to the service. + In many cases it will start the service, but if the service + is already there it just allocates the service proxy and acts + like it was no big deal. */ +static void +start_service (IndicatorServiceManager * service) +{ + IndicatorServiceManagerPrivate * priv = indicator_service_manager_get_instance_private(service); + + g_return_if_fail(priv->name != NULL); + + if (priv->service_proxy_cancel != NULL) { + /* A service proxy is being gotten currently */ + return; + } + + if (priv->service_proxy != NULL) { + g_object_unref(priv->service_proxy); + priv->service_proxy = NULL; + } + + priv->service_proxy_cancel = g_cancellable_new(); + + g_dbus_proxy_new_for_bus(G_BUS_TYPE_SESSION, + G_DBUS_PROXY_FLAGS_NONE, + interface_info, + priv->name, + INDICATOR_SERVICE_OBJECT, + INDICATOR_SERVICE_INTERFACE, + priv->service_proxy_cancel, + service_proxy_cb, + service); + + return; +} + +/* Callback from trying to create the proxy for the service, this + could include starting the service. Sometime it'll fail and + we'll try to start that dang service again! */ +static void +service_proxy_cb (__attribute__((unused)) GObject * object, GAsyncResult * res, gpointer user_data) +{ + GError * error = NULL; + + IndicatorServiceManager * service = INDICATOR_SERVICE_MANAGER(user_data); + g_return_if_fail(service != NULL); + + GDBusProxy * proxy = g_dbus_proxy_new_for_bus_finish(res, &error); + + IndicatorServiceManagerPrivate * priv = indicator_service_manager_get_instance_private(user_data); + + if (priv->service_proxy_cancel != NULL) { + g_object_unref(priv->service_proxy_cancel); + priv->service_proxy_cancel = NULL; + } + + if (error != NULL) { + /* Unable to create the proxy, eh, let's try again + in a bit */ + g_error_free(error); + start_service_again(service); + return; + } + + gchar * name = g_dbus_proxy_get_name_owner(proxy); + if (name == NULL) { + /* Hmm, since creating the proxy should start it, it seems very + odd that it wouldn't have an owner at this point. But, all + we can do is try again. */ + g_object_unref(proxy); + start_service_again(service); + return; + } + g_free(name); + + /* Okay, we're good to grab the proxy at this point, we're + sure that it's ours. */ + priv->service_proxy = proxy; + + /* Signal for drop */ + priv->name_watcher = g_dbus_connection_signal_subscribe( + g_dbus_proxy_get_connection(proxy), + "org.freedesktop.DBus", + "org.freedesktop.DBus", + "NameOwnerChanged", + "/org/freedesktop/DBus", + g_dbus_proxy_get_name(proxy), + G_DBUS_SIGNAL_FLAGS_NONE, + service_proxy_name_changed, + user_data, + NULL); + + /* Build cancelable if we need it */ + if (priv->watch_cancel == NULL) { + priv->watch_cancel = g_cancellable_new(); + } + + /* Send watch */ + g_dbus_proxy_call(priv->service_proxy, + "Watch", + NULL, /* params */ + G_DBUS_CALL_FLAGS_NONE, + -1, + priv->watch_cancel, + watch_cb, + user_data); + + return; +} + +/* Responds to the name owner changing of the proxy, this + usually means the service died. We're dropping the proxy + and recreating it so that it'll restart the service. */ +static void +service_proxy_name_changed (__attribute__((unused)) GDBusConnection * connection, + __attribute__((unused)) const gchar * sender_name, + __attribute__((unused)) const gchar * object_path, + __attribute__((unused)) const gchar * interface_name, + __attribute__((unused)) const gchar * signal_name, + GVariant * parameters, + gpointer user_data) + +{ + IndicatorServiceManagerPrivate * priv = indicator_service_manager_get_instance_private(user_data); + + const gchar * new_name = NULL; + const gchar * prev_name = NULL; + g_variant_get(parameters, "(&s&s&s)", NULL, &prev_name, &new_name); + + if (new_name == NULL || new_name[0] == 0) { + if (priv->connected) { + priv->connected = FALSE; + g_signal_emit(G_OBJECT(user_data), signals[CONNECTION_CHANGE], 0, FALSE, TRUE); + } + + start_service_again(INDICATOR_SERVICE_MANAGER(user_data)); + } else { + /* If we weren't connected before, we are now. Let's tell the + world! */ + if (!priv->connected) { + priv->connected = TRUE; + g_signal_emit(G_OBJECT(user_data), signals[CONNECTION_CHANGE], 0, TRUE, TRUE); + } + + /* If the names are both valid, and they're not the same, it means that + we've actually changed. So we need to tell the new guy that we're + watching them */ + if (new_name != NULL && prev_name != NULL && new_name[0] != 0 && prev_name != 0 && g_strcmp0(prev_name, new_name) != 0) { + /* Send watch */ + g_dbus_proxy_call(priv->service_proxy, + "Watch", + NULL, /* params */ + G_DBUS_CALL_FLAGS_NONE, + -1, + priv->watch_cancel, + watch_cb, + user_data); + } + } + + return; +} + +/* The callback that starts the service for real after + the timeout as determined in 'start_service_again'. + This could be in the idle or a timer. */ +static gboolean +start_service_again_cb (gpointer data) +{ + IndicatorServiceManagerPrivate * priv = indicator_service_manager_get_instance_private(data); + priv->restart_count++; + g_debug("Restarting service '%s' count %d", priv->name, priv->restart_count); + start_service(INDICATOR_SERVICE_MANAGER(data)); + priv->restart_source = 0; + return FALSE; +} + +/* This function tries to start a new service, perhaps + after a timeout that it determines. The real issue + here is that it throttles restarting if we're not + being successful. */ +static void +start_service_again (IndicatorServiceManager * manager) +{ + IndicatorServiceManagerPrivate * priv = indicator_service_manager_get_instance_private(manager); + + /* If we've already got a restart source running then + let's not do this again. */ + if (priv->restart_source != 0) { + return; + } + + /* Allow the restarting to be disabled */ + if (g_getenv(TIMEOUT_ENV_NAME)) { + return; + } + + if (priv->restart_count == 0) { + /* First time, do it in idle */ + g_idle_add(start_service_again_cb, manager); + } else { + /* Not our first time 'round the block. Let's slow this down. */ + if (priv->restart_count > 16) + priv->restart_count = 16; /* Not more than 1024x */ + priv->restart_source = g_timeout_add((1 << priv->restart_count) * TIMEOUT_MULTIPLIER, start_service_again_cb, manager); + } + + return; +} + +/* API */ + +/** + indicator_service_manager_new: + @dbus_name: The well known name of the service on DBus + + This creates a new service manager object. If the service + is not running it will start it. No matter what, it will + give a IndicatorServiceManager::connection-changed event + signal when it gets connected. + + Return value: A brand new lovely #IndicatorServiceManager + object. +*/ +IndicatorServiceManager * +indicator_service_manager_new (const gchar * dbus_name) +{ + GObject * obj = g_object_new(INDICATOR_SERVICE_MANAGER_TYPE, + PROP_NAME_S, dbus_name, + NULL); + + return INDICATOR_SERVICE_MANAGER(obj); +} + +/** + inicator_service_manager_new_version: + @dbus_name: The well known name of the service on DBus + @version: Version of the service we expect + + This creates a new service manager object. It also sets + the version of the service that we're expecting to see. + In general, it behaves similarly to #indicator_service_manager_new() + except that it checks @version against the version returned + by the service. + + Return value: A brand new lovely #IndicatorServiceManager + object. +*/ +IndicatorServiceManager * +indicator_service_manager_new_version (const gchar * dbus_name, guint version) +{ + GObject * obj = g_object_new(INDICATOR_SERVICE_MANAGER_TYPE, + PROP_NAME_S, dbus_name, + PROP_VERSION_S, version, + NULL); + + return INDICATOR_SERVICE_MANAGER(obj); +} + +/** + indicator_service_manager_connected: + @sm: #IndicatorServiceManager object to check + + Checks to see if the service manager is connected to a + service. + + Return value: #TRUE if there is a service connceted. +*/ +gboolean +indicator_service_manager_connected (IndicatorServiceManager * sm) +{ + g_return_val_if_fail(INDICATOR_IS_SERVICE_MANAGER(sm), FALSE); + IndicatorServiceManagerPrivate * priv = indicator_service_manager_get_instance_private(sm); + return priv->connected; +} + +/** + indicator_service_manager_set_refresh: + @sm: #IndicatorServiceManager object to configure + @time_in_ms: The refresh time in milliseconds + + Use this function to set the amount of time between restarting + services that may crash or shutdown. This is mostly useful + for testing and development. + + NOTE: Not yet implemented. +*/ +void +indicator_service_manager_set_refresh (__attribute__((unused)) IndicatorServiceManager * sm, __attribute__((unused)) guint time_in_ms) +{ + + return; +} -- cgit v1.2.3