/* SPDX-License-Identifier: LGPL-2.1-or-later */ /* * Copyright (C) 2018 Red Hat, Inc. */ #include "nm-default.h" #include "nm-keep-alive.h" #include "settings/nm-settings-connection.h" #include "nm-glib-aux/nm-dbus-aux.h" /*****************************************************************************/ NM_GOBJECT_PROPERTIES_DEFINE(NMKeepAlive, PROP_ALIVE, ); typedef struct { GObject *owner; NMSettingsConnection *connection; GDBusConnection * dbus_connection; char * dbus_client; GCancellable *dbus_client_confirm_cancellable; guint subscription_id; bool armed : 1; bool disarmed : 1; bool alive : 1; bool dbus_client_confirmed : 1; bool dbus_client_watching : 1; bool connection_was_visible : 1; } NMKeepAlivePrivate; struct _NMKeepAlive { GObject parent; NMKeepAlivePrivate _priv; }; struct _NMKeepAliveClass { GObjectClass parent; }; G_DEFINE_TYPE(NMKeepAlive, nm_keep_alive, G_TYPE_OBJECT) #define NM_KEEP_ALIVE_GET_PRIVATE(self) _NM_GET_PRIVATE(self, NMKeepAlive, NM_IS_KEEP_ALIVE) /*****************************************************************************/ #define _NMLOG_DOMAIN LOGD_CORE #define _NMLOG(level, ...) __NMLOG_DEFAULT(level, _NMLOG_DOMAIN, "keep-alive", __VA_ARGS__) /*****************************************************************************/ static gboolean _is_alive_dbus_client(NMKeepAlive *self); static void cleanup_dbus_watch(NMKeepAlive *self); /*****************************************************************************/ static gboolean _is_alive(NMKeepAlive *self) { NMKeepAlivePrivate *priv = NM_KEEP_ALIVE_GET_PRIVATE(self); nm_assert(!priv->disarmed); if (!priv->armed) { /* before arming, the instance is always alive. */ return TRUE; } if (priv->dbus_client_watching) { if (_is_alive_dbus_client(self)) { /* no matter what, the keep-alive is alive, because there is a D-Bus client * still around keeping it alive. */ return TRUE; } /* the D-Bus client is gone. The only other binding (below) for the connection's * visibility cannot keep the instance alive. * * As such, a D-Bus client watch is authoritative and overrules other conditions (that * we have so far). */ return FALSE; } if (priv->connection && priv->connection_was_visible && !NM_FLAGS_HAS(nm_settings_connection_get_flags(priv->connection), NM_SETTINGS_CONNECTION_INT_FLAGS_VISIBLE)) { /* note that we only declare the keep-alive as dead due to invisible * connection, if * (1) we monitor a connection, obviously * (2) the connection was visible earlier and is no longer. It was * was invisible all the time, it does not suffice. */ return FALSE; } /* by default, the instance is alive. */ return TRUE; } static void _notify_alive(NMKeepAlive *self) { NMKeepAlivePrivate *priv = NM_KEEP_ALIVE_GET_PRIVATE(self); if (priv->disarmed) { /* once disarmed, the alive state is frozen. */ return; } if (priv->alive == _is_alive(self)) return; priv->alive = !priv->alive; _LOGD("instance is now %s", priv->alive ? "alive" : "dead"); _notify(self, PROP_ALIVE); } gboolean nm_keep_alive_is_alive(NMKeepAlive *self) { return NM_KEEP_ALIVE_GET_PRIVATE(self)->alive; } /*****************************************************************************/ static void connection_flags_changed(NMSettingsConnection *connection, NMKeepAlive *self) { NMKeepAlivePrivate *priv = NM_KEEP_ALIVE_GET_PRIVATE(self); if (!priv->connection_was_visible && NM_FLAGS_HAS(nm_settings_connection_get_flags(priv->connection), NM_SETTINGS_CONNECTION_INT_FLAGS_VISIBLE)) { /* the profile was never visible but now it becomes visible. * Remember that. * * Before this happens (that is, if the device was invisible all along), * the keep alive instance is considered alive (w.r.t. watching the connection). * * The reason is to allow a user to manually activate an invisible profile and keep * it alive. At least, as long until the user logs out the first time (which is the * first time, the profiles changes from visible to invisible). * * Yes, that is odd. How to improve? */ priv->connection_was_visible = TRUE; } _notify_alive(self); } static void _set_settings_connection_watch_visible(NMKeepAlive * self, NMSettingsConnection *connection, gboolean emit_signal) { NMKeepAlivePrivate *priv = NM_KEEP_ALIVE_GET_PRIVATE(self); gs_unref_object NMSettingsConnection *old_connection = NULL; if (priv->connection == connection) return; if (priv->connection) { g_signal_handlers_disconnect_by_func(priv->connection, G_CALLBACK(connection_flags_changed), self); old_connection = g_steal_pointer(&priv->connection); } if (connection && !priv->disarmed) { priv->connection = g_object_ref(connection); priv->connection_was_visible = NM_FLAGS_HAS(nm_settings_connection_get_flags(priv->connection), NM_SETTINGS_CONNECTION_INT_FLAGS_VISIBLE); g_signal_connect(priv->connection, NM_SETTINGS_CONNECTION_FLAGS_CHANGED, G_CALLBACK(connection_flags_changed), self); } if (emit_signal) _notify_alive(self); } void nm_keep_alive_set_settings_connection_watch_visible(NMKeepAlive * self, NMSettingsConnection *connection) { _set_settings_connection_watch_visible(self, connection, TRUE); } /*****************************************************************************/ static void get_name_owner_cb(const char *name_owner, GError *error, gpointer user_data) { NMKeepAlive * self; NMKeepAlivePrivate *priv; if (!name_owner && g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) return; self = user_data; priv = NM_KEEP_ALIVE_GET_PRIVATE(self); if (name_owner && nm_streq(name_owner, priv->dbus_client)) { /* all good, the name is confirmed. */ return; } _LOGD("DBus client for keep alive is not on the bus"); cleanup_dbus_watch(self); _notify_alive(self); } static gboolean _is_alive_dbus_client(NMKeepAlive *self) { NMKeepAlivePrivate *priv = NM_KEEP_ALIVE_GET_PRIVATE(self); if (!priv->dbus_client) return FALSE; if (!priv->dbus_client_confirmed) { /* it's unconfirmed that the D-Bus client is really alive. * It looks like it is, but as we are claiming that to be * the case, issue an async GetNameOwner call to make sure. */ priv->dbus_client_confirmed = TRUE; priv->dbus_client_confirm_cancellable = g_cancellable_new(); nm_dbus_connection_call_get_name_owner(priv->dbus_connection, priv->dbus_client, -1, priv->dbus_client_confirm_cancellable, get_name_owner_cb, self); } return TRUE; } static void cleanup_dbus_watch(NMKeepAlive *self) { NMKeepAlivePrivate *priv = NM_KEEP_ALIVE_GET_PRIVATE(self); if (!priv->dbus_client) return; _LOGD("Cleanup DBus client watch"); nm_clear_g_cancellable(&priv->dbus_client_confirm_cancellable); nm_clear_g_free(&priv->dbus_client); if (priv->dbus_connection) { g_dbus_connection_signal_unsubscribe(priv->dbus_connection, nm_steal_int(&priv->subscription_id)); g_clear_object(&priv->dbus_connection); } } static void name_owner_changed_cb(GDBusConnection *connection, const char * sender_name, const char * object_path, const char * interface_name, const char * signal_name, GVariant * parameters, gpointer user_data) { NMKeepAlive *self = NM_KEEP_ALIVE(user_data); const char * old_owner; const char * new_owner; g_variant_get(parameters, "(&s&s&s)", NULL, &old_owner, &new_owner); if (!nm_streq0(new_owner, "")) return; _LOGD("DBus client for keep alive disappeared from bus"); cleanup_dbus_watch(self); _notify_alive(self); } void nm_keep_alive_set_dbus_client_watch(NMKeepAlive * self, GDBusConnection *connection, const char * client_address) { NMKeepAlivePrivate *priv = NM_KEEP_ALIVE_GET_PRIVATE(self); if (priv->disarmed) return; cleanup_dbus_watch(self); if (client_address) { _LOGD("Registering dbus client watch for keep alive"); priv->dbus_client = g_strdup(client_address); priv->dbus_client_watching = TRUE; priv->dbus_client_confirmed = FALSE; priv->dbus_connection = g_object_ref(connection); priv->subscription_id = nm_dbus_connection_signal_subscribe_name_owner_changed(priv->dbus_connection, priv->dbus_client, name_owner_changed_cb, self, NULL); } else priv->dbus_client_watching = FALSE; _notify_alive(self); } /*****************************************************************************/ /** * nm_keep_alive_arm: * @self: the #NMKeepAlive * * A #NMKeepAlive instance is unarmed by default. That means, it's * alive and stays alive until being armed. Arming means, that the conditions * start to be actively evaluated, that the alive state may change, and * that property changed signals are emitted. * * The opposite is nm_keep_alive_disarm() which freezes the alive state * for good. Once disarmed, the instance cannot be armed again. Arming an * instance multiple times has no effect. Arming an already disarmed instance * also has no effect. */ void nm_keep_alive_arm(NMKeepAlive *self) { NMKeepAlivePrivate *priv = NM_KEEP_ALIVE_GET_PRIVATE(self); if (!priv->armed) { priv->armed = TRUE; _notify_alive(self); } } /** * nm_keep_alive_disarm: * @self: the #NMKeepAlive instance * * Once the instance is disarmed, it will not change its alive state * anymore and will not emit anymore property changed signals about * alive state changed. * * As such, it will also free internal resources (since they no longer * affect the externally visible state). * * Once disarmed, the instance is frozen and cannot change anymore. */ void nm_keep_alive_disarm(NMKeepAlive *self) { NMKeepAlivePrivate *priv = NM_KEEP_ALIVE_GET_PRIVATE(self); priv->disarmed = TRUE; /* release internal data. */ _set_settings_connection_watch_visible(self, NULL, FALSE); cleanup_dbus_watch(self); } /** * nm_keep_alive_destroy: * @self: (allow-none): the #NMKeepAlive instance to destroy. * * This does 3 things in one: * * - set owner to %NULL * - disarm the instance. * - unref @self. */ void nm_keep_alive_destroy(NMKeepAlive *self) { if (!self) return; _nm_keep_alive_set_owner(self, NULL); nm_keep_alive_disarm(self); g_object_unref(self); } /*****************************************************************************/ static void get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { NMKeepAlive *self = NM_KEEP_ALIVE(object); switch (prop_id) { case PROP_ALIVE: g_value_set_boolean(value, nm_keep_alive_is_alive(self)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } /*****************************************************************************/ /** * nm_keep_alive_get_owner: * @self: the #NMKeepAlive * * Returns: the owner instance associated with this @self. This commonly * is set to be the target instance, which @self guards for being alive. * Returns a gpointer, but of course it's some GObject instance. */ gpointer /* GObject * */ nm_keep_alive_get_owner(NMKeepAlive *self) { NMKeepAlivePrivate *priv = NM_KEEP_ALIVE_GET_PRIVATE(self); nm_assert(!priv->owner || G_IS_OBJECT(priv->owner)); return priv->owner; } /** * _nm_keep_alive_set_owner: * @self: the #NMKeepAlive * @owner: the owner to set or unset. * * Sets or unsets the owner instance. Think of the owner the target * instance that is guarded by @self. It's the responsibility of the * owner to set and properly unset this pointer. As the owner also * controls the lifetime of the NMKeepAlive instance. * * This API is not to be called by everybody, but only the owner of * @self. */ void _nm_keep_alive_set_owner(NMKeepAlive *self, GObject *owner) { NMKeepAlivePrivate *priv = NM_KEEP_ALIVE_GET_PRIVATE(self); nm_assert(!owner || G_IS_OBJECT(owner)); /* it's bad style to reset the owner object. You are supposed to * set it once, and clear it once. That's it. */ nm_assert(!owner || !priv->owner); /* optimally, we would take a reference to @owner. But the * owner already owns a reference to the keep-alive, so we cannot * just own a reference back. * * We could register a weak-pointer here. But instead, declare that * owner is required to set itself as owner when creating the * keep-alive instance, and unset itself when it lets go of the * keep-alive instance (at latest, when the owner itself gets destroyed). */ priv->owner = owner; } /*****************************************************************************/ static void nm_keep_alive_init(NMKeepAlive *self) { NMKeepAlivePrivate *priv = NM_KEEP_ALIVE_GET_PRIVATE(self); priv->alive = TRUE; nm_assert(priv->alive == _is_alive(self)); } NMKeepAlive * nm_keep_alive_new(void) { return g_object_new(NM_TYPE_KEEP_ALIVE, NULL); } static void dispose(GObject *object) { NMKeepAlive *self = NM_KEEP_ALIVE(object); nm_assert(!NM_KEEP_ALIVE_GET_PRIVATE(self)->owner); /* disarm also happens to free all resources. */ nm_keep_alive_disarm(self); } static void nm_keep_alive_class_init(NMKeepAliveClass *keep_alive_class) { GObjectClass *object_class = G_OBJECT_CLASS(keep_alive_class); object_class->get_property = get_property; object_class->dispose = dispose; obj_properties[PROP_ALIVE] = g_param_spec_string(NM_KEEP_ALIVE_ALIVE, "", "", NULL, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties); }