diff options
Diffstat (limited to 'src/nm-dbus-manager.c')
| -rw-r--r-- | src/nm-dbus-manager.c | 1626 |
1 files changed, 1626 insertions, 0 deletions
diff --git a/src/nm-dbus-manager.c b/src/nm-dbus-manager.c new file mode 100644 index 00000000..3e369129 --- /dev/null +++ b/src/nm-dbus-manager.c @@ -0,0 +1,1626 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager -- Network link manager + * + * 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. + * + * 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, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright (C) 2006 - 2013 Red Hat, Inc. + * Copyright (C) 2006 - 2008 Novell, Inc. + */ + +#include "nm-default.h" + +#include "nm-dbus-manager.h" + +#include <unistd.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <errno.h> +#include <string.h> + +#include "c-list/src/c-list.h" +#include "nm-dbus-interface.h" +#include "nm-core-internal.h" +#include "nm-dbus-compat.h" +#include "nm-dbus-object.h" +#include "NetworkManagerUtils.h" + +/* The base path for our GDBusObjectManagerServers. They do not contain + * "NetworkManager" because GDBusObjectManagerServer requires that all + * exported objects be *below* the base path, and eg the Manager object + * is the base path already. + */ +#define OBJECT_MANAGER_SERVER_BASE_PATH "/org/freedesktop" + +/*****************************************************************************/ + +typedef struct { + GVariant *value; +} PropertyCacheData; + +typedef struct { + CList registration_lst; + NMDBusObject *obj; + NMDBusObjectClass *klass; + guint info_idx; + guint registration_id; + PropertyCacheData property_cache[]; +} RegistrationData; + +/* we require that @path is the first member of NMDBusManagerData + * because _objects_by_path_hash() requires that. */ +G_STATIC_ASSERT (G_STRUCT_OFFSET (struct _NMDBusObjectInternal, path) == 0); + +enum { + PRIVATE_CONNECTION_NEW, + PRIVATE_CONNECTION_DISCONNECTED, + + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL]; + +typedef struct { + GHashTable *objects_by_path; + CList objects_lst_head; + + CList private_servers_lst_head; + + NMDBusManagerSetPropertyHandler set_property_handler; + gpointer set_property_handler_data; + + GDBusConnection *connection; + GDBusProxy *proxy; + guint objmgr_registration_id; +} NMDBusManagerPrivate; + +struct _NMDBusManager { + GObject parent; + NMDBusManagerPrivate _priv; +}; + +struct _NMDBusManagerClass { + GObjectClass parent; +}; + +G_DEFINE_TYPE(NMDBusManager, nm_dbus_manager, G_TYPE_OBJECT) + +#define NM_DBUS_MANAGER_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMDBusManager, NM_IS_DBUS_MANAGER) + +/*****************************************************************************/ + +#define _NMLOG_DOMAIN LOGD_CORE +#define _NMLOG(level, ...) __NMLOG_DEFAULT (level, _NMLOG_DOMAIN, "bus-manager", __VA_ARGS__) + +NM_DEFINE_SINGLETON_GETTER (NMDBusManager, nm_dbus_manager_get, NM_TYPE_DBUS_MANAGER); + +/*****************************************************************************/ + +static const GDBusInterfaceInfo interface_info_objmgr; +static const GDBusSignalInfo signal_info_objmgr_interfaces_added; +static const GDBusSignalInfo signal_info_objmgr_interfaces_removed; +static GVariantBuilder *_obj_collect_properties_all (NMDBusObject *obj, + GVariantBuilder *builder); + +/*****************************************************************************/ + +static guint +_objects_by_path_hash (gconstpointer user_data) +{ + const char *const*p_data = user_data; + + nm_assert (p_data); + nm_assert (*p_data); + nm_assert ((*p_data)[0] == '/'); + + return nm_hash_str (*p_data); +} + +static gboolean +_objects_by_path_equal (gconstpointer user_data_a, gconstpointer user_data_b) +{ + const char *const*p_data_a = user_data_a; + const char *const*p_data_b = user_data_b; + + nm_assert (p_data_a); + nm_assert (*p_data_a); + nm_assert ((*p_data_a)[0] == '/'); + nm_assert (p_data_b); + nm_assert (*p_data_b); + nm_assert ((*p_data_b)[0] == '/'); + + return nm_streq (*p_data_a, *p_data_b); +} + +/*****************************************************************************/ + +typedef struct { + CList private_servers_lst; + + const char *tag; + GQuark detail; + char *address; + GDBusServer *server; + + /* With peer bus connections, we'll get a new connection for each + * client. For each connection we create an ObjectManager for + * that connection to handle exporting our objects. + * + * Note that even for connections that don't export any objects + * we'll still create GDBusObjectManager since that's where we store + * the pointer to the GDBusConnection. + */ + CList object_mgr_lst_head; + + NMDBusManager *manager; +} PrivateServer; + +typedef struct { + CList object_mgr_lst; + GDBusObjectManagerServer *manager; + char *fake_sender; +} ObjectMgrData; + +typedef struct { + GDBusConnection *connection; + PrivateServer *server; + gboolean remote_peer_vanished; +} CloseConnectionInfo; + +/*****************************************************************************/ + +static void +_object_mgr_data_free (ObjectMgrData *obj_mgr_data) +{ + GDBusConnection *connection; + + c_list_unlink_stale (&obj_mgr_data->object_mgr_lst); + + connection = g_dbus_object_manager_server_get_connection (obj_mgr_data->manager); + if (!g_dbus_connection_is_closed (connection)) + g_dbus_connection_close (connection, NULL, NULL, NULL); + g_dbus_object_manager_server_set_connection (obj_mgr_data->manager, NULL); + g_object_unref (obj_mgr_data->manager); + g_object_unref (connection); + + g_free (obj_mgr_data->fake_sender); + + g_slice_free (ObjectMgrData, obj_mgr_data); +} + +/*****************************************************************************/ + +static gboolean +close_connection_in_idle (gpointer user_data) +{ + CloseConnectionInfo *info = user_data; + PrivateServer *server = info->server; + ObjectMgrData *obj_mgr_data, *obj_mgr_data_safe; + + /* Emit this for the manager */ + g_signal_emit (server->manager, + signals[PRIVATE_CONNECTION_DISCONNECTED], + server->detail, + info->connection); + + /* FIXME: there's a bug (754730) in GLib for which the connection + * is marked as closed when the remote peer vanishes but its + * resources are not cleaned up. Work around it by explicitly + * closing the connection in that case. */ + if (info->remote_peer_vanished) + g_dbus_connection_close (info->connection, NULL, NULL, NULL); + + c_list_for_each_entry_safe (obj_mgr_data, obj_mgr_data_safe, &server->object_mgr_lst_head, object_mgr_lst) { + gs_unref_object GDBusConnection *connection = NULL; + + connection = g_dbus_object_manager_server_get_connection (obj_mgr_data->manager); + if (connection == info->connection) { + _object_mgr_data_free (obj_mgr_data); + break; + } + } + + g_object_unref (server->manager); + g_slice_free (CloseConnectionInfo, info); + + return G_SOURCE_REMOVE; +} + +static void +private_server_closed_connection (GDBusConnection *conn, + gboolean remote_peer_vanished, + GError *error, + gpointer user_data) +{ + PrivateServer *s = user_data; + CloseConnectionInfo *info; + + /* Clean up after the connection */ + _LOGD ("(%s) closed connection %p on private socket", s->tag, conn); + + info = g_slice_new0 (CloseConnectionInfo); + info->connection = conn; + info->server = s; + info->remote_peer_vanished = remote_peer_vanished; + + g_object_ref (s->manager); + + /* Delay the close of connection to ensure that D-Bus signals + * are handled */ + g_idle_add (close_connection_in_idle, info); +} + +static gboolean +private_server_new_connection (GDBusServer *server, + GDBusConnection *conn, + gpointer user_data) +{ + PrivateServer *s = user_data; + ObjectMgrData *obj_mgr_data; + static guint32 counter = 0; + GDBusObjectManagerServer *manager; + char *sender; + + g_signal_connect (conn, "closed", G_CALLBACK (private_server_closed_connection), s); + + /* Fake a sender since private connections don't have one */ + sender = g_strdup_printf ("x:y:%d", counter++); + + manager = g_dbus_object_manager_server_new (OBJECT_MANAGER_SERVER_BASE_PATH); + g_dbus_object_manager_server_set_connection (manager, conn); + + obj_mgr_data = g_slice_new (ObjectMgrData); + obj_mgr_data->manager = manager; + obj_mgr_data->fake_sender = sender; + c_list_link_tail (&s->object_mgr_lst_head, &obj_mgr_data->object_mgr_lst); + + _LOGD ("(%s) accepted connection %p on private socket", s->tag, conn); + + /* Emit this for the manager. + * + * It is essential to do this from the "new-connection" signal handler, as + * at that point no messages from the connection are yet processed + * (which avoids races with registering objects). */ + g_signal_emit (s->manager, + signals[PRIVATE_CONNECTION_NEW], + s->detail, + conn, + manager); + return TRUE; +} + +static gboolean +private_server_authorize (GDBusAuthObserver *observer, + GIOStream *stream, + GCredentials *credentials, + gpointer user_data) +{ + return g_credentials_get_unix_user (credentials, NULL) == 0; +} + +static gboolean +private_server_allow_mechanism (GDBusAuthObserver *observer, + const char *mechanism, + gpointer user_data) +{ + return NM_IN_STRSET (mechanism, "EXTERNAL"); +} + +static void +private_server_free (gpointer ptr) +{ + PrivateServer *s = ptr; + ObjectMgrData *obj_mgr_data, *obj_mgr_data_safe; + + c_list_unlink_stale (&s->private_servers_lst); + + unlink (s->address); + g_free (s->address); + + c_list_for_each_entry_safe (obj_mgr_data, obj_mgr_data_safe, &s->object_mgr_lst_head, object_mgr_lst) + _object_mgr_data_free (obj_mgr_data); + + g_dbus_server_stop (s->server); + + g_signal_handlers_disconnect_by_func (s->server, G_CALLBACK (private_server_new_connection), s); + + g_object_unref (s->server); + + g_slice_free (PrivateServer, s); +} + +void +nm_dbus_manager_private_server_register (NMDBusManager *self, + const char *path, + const char *tag) +{ + NMDBusManagerPrivate *priv; + PrivateServer *s; + gs_unref_object GDBusAuthObserver *auth_observer = NULL; + GDBusServer *server; + GError *error = NULL; + gs_free char *address = NULL; + gs_free char *guid = NULL; + + g_return_if_fail (NM_IS_DBUS_MANAGER (self)); + g_return_if_fail (path); + g_return_if_fail (tag); + + priv = NM_DBUS_MANAGER_GET_PRIVATE (self); + + /* Only one instance per tag; but don't warn */ + c_list_for_each_entry (s, &priv->private_servers_lst_head, private_servers_lst) { + if (nm_streq0 (tag, s->tag)) + return; + } + + unlink (path); + address = g_strdup_printf ("unix:path=%s", path); + + _LOGD ("(%s) creating private socket %s", tag, address); + + guid = g_dbus_generate_guid (); + auth_observer = g_dbus_auth_observer_new (); + g_signal_connect (auth_observer, "authorize-authenticated-peer", + G_CALLBACK (private_server_authorize), NULL); + g_signal_connect (auth_observer, "allow-mechanism", + G_CALLBACK (private_server_allow_mechanism), NULL); + server = g_dbus_server_new_sync (address, + G_DBUS_SERVER_FLAGS_NONE, + guid, + auth_observer, + NULL, &error); + + if (!server) { + _LOGW ("(%s) failed to set up private socket %s: %s", + tag, address, error->message); + g_error_free (error); + return; + } + + s = g_slice_new0 (PrivateServer); + s->address = g_steal_pointer (&address); + s->server = server; + g_signal_connect (server, "new-connection", + G_CALLBACK (private_server_new_connection), s); + + c_list_init (&s->object_mgr_lst_head); + + s->manager = self; + s->detail = g_quark_from_string (tag); + s->tag = g_quark_to_string (s->detail); + + c_list_link_tail (&priv->private_servers_lst_head, &s->private_servers_lst); + + g_dbus_server_start (server); +} + +static const char * +private_server_get_connection_owner (PrivateServer *s, GDBusConnection *connection) +{ + ObjectMgrData *obj_mgr_data; + + nm_assert (s); + nm_assert (G_IS_DBUS_CONNECTION (connection)); + + c_list_for_each_entry (obj_mgr_data, &s->object_mgr_lst_head, object_mgr_lst) { + gs_unref_object GDBusConnection *c = NULL; + + c = g_dbus_object_manager_server_get_connection (obj_mgr_data->manager); + if (c == connection) + return obj_mgr_data->fake_sender; + } + return NULL; +} + +static GDBusConnection * +private_server_get_connection_by_owner (PrivateServer *s, const char *owner) +{ + ObjectMgrData *obj_mgr_data; + + nm_assert (s); + nm_assert (owner); + + c_list_for_each_entry (obj_mgr_data, &s->object_mgr_lst_head, object_mgr_lst) { + if (nm_streq (owner, obj_mgr_data->fake_sender)) + return g_dbus_object_manager_server_get_connection (obj_mgr_data->manager); + } + return NULL; +} + +/*****************************************************************************/ + +static gboolean +_bus_get_unix_pid (NMDBusManager *self, + const char *sender, + gulong *out_pid, + GError **error) +{ + guint32 unix_pid = G_MAXUINT32; + gs_unref_variant GVariant *ret = NULL; + + ret = _nm_dbus_proxy_call_sync (NM_DBUS_MANAGER_GET_PRIVATE (self)->proxy, + "GetConnectionUnixProcessID", + g_variant_new ("(s)", sender), + G_VARIANT_TYPE ("(u)"), + G_DBUS_CALL_FLAGS_NONE, 2000, + NULL, error); + if (!ret) + return FALSE; + + g_variant_get (ret, "(u)", &unix_pid); + + *out_pid = (gulong) unix_pid; + return TRUE; +} + +static gboolean +_bus_get_unix_user (NMDBusManager *self, + const char *sender, + gulong *out_user, + GError **error) +{ + guint32 unix_uid = G_MAXUINT32; + gs_unref_variant GVariant *ret = NULL; + + ret = _nm_dbus_proxy_call_sync (NM_DBUS_MANAGER_GET_PRIVATE (self)->proxy, + "GetConnectionUnixUser", + g_variant_new ("(s)", sender), + G_VARIANT_TYPE ("(u)"), + G_DBUS_CALL_FLAGS_NONE, 2000, + NULL, error); + if (!ret) + return FALSE; + + g_variant_get (ret, "(u)", &unix_uid); + + *out_user = (gulong) unix_uid; + return TRUE; +} + +/** + * _get_caller_info(): + * + * Given a GDBus method invocation, or a GDBusConnection + GDBusMessage, + * return the sender and the UID of the sender. + */ +static gboolean +_get_caller_info (NMDBusManager *self, + GDBusMethodInvocation *context, + GDBusConnection *connection, + GDBusMessage *message, + char **out_sender, + gulong *out_uid, + gulong *out_pid) +{ + NMDBusManagerPrivate *priv = NM_DBUS_MANAGER_GET_PRIVATE (self); + const char *sender; + + if (context) { + connection = g_dbus_method_invocation_get_connection (context); + + /* only bus connections will have a sender */ + sender = g_dbus_method_invocation_get_sender (context); + } else { + g_assert (message); + sender = g_dbus_message_get_sender (message); + } + g_assert (connection); + + if (!sender) { + PrivateServer *s; + + /* Might be a private connection, for which we fake a sender */ + c_list_for_each_entry (s, &priv->private_servers_lst_head, private_servers_lst) { + sender = private_server_get_connection_owner (s, connection); + if (sender) { + if (out_uid) + *out_uid = 0; + if (out_sender) + *out_sender = g_strdup (sender); + if (out_pid) { + GCredentials *creds; + + creds = g_dbus_connection_get_peer_credentials (connection); + if (creds) { + pid_t pid; + + pid = g_credentials_get_unix_pid (creds, NULL); + if (pid == -1) + *out_pid = G_MAXULONG; + else + *out_pid = pid; + } else + *out_pid = G_MAXULONG; + } + return TRUE; + } + } + return FALSE; + } + + /* Bus connections always have a sender */ + g_assert (sender); + if (out_uid) { + if (!_bus_get_unix_user (self, sender, out_uid, NULL)) { + *out_uid = G_MAXULONG; + return FALSE; + } + } + + if (out_pid) { + if (!_bus_get_unix_pid (self, sender, out_pid, NULL)) { + *out_pid = G_MAXULONG; + return FALSE; + } + } + + if (out_sender) + *out_sender = g_strdup (sender); + + return TRUE; +} + +gboolean +nm_dbus_manager_get_caller_info (NMDBusManager *self, + GDBusMethodInvocation *context, + char **out_sender, + gulong *out_uid, + gulong *out_pid) +{ + return _get_caller_info (self, context, NULL, NULL, out_sender, out_uid, out_pid); +} + +gboolean +nm_dbus_manager_get_caller_info_from_message (NMDBusManager *self, + GDBusConnection *connection, + GDBusMessage *message, + char **out_sender, + gulong *out_uid, + gulong *out_pid) +{ + return _get_caller_info (self, NULL, connection, message, out_sender, out_uid, out_pid); +} + +/** + * nm_dbus_manager_ensure_uid: + * + * @self: bus manager instance + * @context: D-Bus method invocation + * @uid: a user-id + * @error_domain: error domain to return on failure + * @error_code: error code to return on failure + * + * Retrieves the uid of the D-Bus method caller and + * checks that it matches @uid, unless @uid is G_MAXULONG. + * In case of failure the function returns FALSE and finishes + * handling the D-Bus method with an error. + * + * Returns: %TRUE if the check succeeded, %FALSE otherwise + */ +gboolean +nm_dbus_manager_ensure_uid (NMDBusManager *self, + GDBusMethodInvocation *context, + gulong uid, + GQuark error_domain, + int error_code) +{ + gulong caller_uid; + GError *error = NULL; + + g_return_val_if_fail (NM_IS_DBUS_MANAGER (self), FALSE); + g_return_val_if_fail (G_IS_DBUS_METHOD_INVOCATION (context), FALSE); + + if (!nm_dbus_manager_get_caller_info (self, context, NULL, &caller_uid, NULL)) { + error = g_error_new_literal (error_domain, + error_code, + "Unable to determine request UID."); + g_dbus_method_invocation_take_error (context, error); + return FALSE; + } + + if (uid != G_MAXULONG && caller_uid != uid) { + error = g_error_new_literal (error_domain, + error_code, + "Permission denied"); + g_dbus_method_invocation_take_error (context, error); + return FALSE; + } + + return TRUE; +} + +gboolean +nm_dbus_manager_get_unix_user (NMDBusManager *self, + const char *sender, + gulong *out_uid) +{ + NMDBusManagerPrivate *priv = NM_DBUS_MANAGER_GET_PRIVATE (self); + PrivateServer *s; + GError *error = NULL; + + g_return_val_if_fail (sender != NULL, FALSE); + g_return_val_if_fail (out_uid != NULL, FALSE); + + /* Check if it's a private connection sender, which we fake */ + c_list_for_each_entry (s, &priv->private_servers_lst_head, private_servers_lst) { + gs_unref_object GDBusConnection *connection = NULL; + + connection = private_server_get_connection_by_owner (s, sender); + if (connection) { + *out_uid = 0; + return TRUE; + } + } + + /* Otherwise, a bus connection */ + if (!_bus_get_unix_user (self, sender, out_uid, &error)) { + _LOGW ("failed to get unix user for dbus sender '%s': %s", + sender, error->message); + g_error_free (error); + return FALSE; + } + + return TRUE; +} + +/*****************************************************************************/ + +const char * +nm_dbus_manager_connection_get_private_name (NMDBusManager *self, + GDBusConnection *connection) +{ + NMDBusManagerPrivate *priv; + PrivateServer *s; + const char *owner; + + g_return_val_if_fail (NM_IS_DBUS_MANAGER (self), FALSE); + g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), FALSE); + + if (g_dbus_connection_get_unique_name (connection)) { + /* Shortcut. The connection is not a private connection. */ + return NULL; + } + + priv = NM_DBUS_MANAGER_GET_PRIVATE (self); + c_list_for_each_entry (s, &priv->private_servers_lst_head, private_servers_lst) { + if ((owner = private_server_get_connection_owner (s, connection))) + return owner; + } + g_return_val_if_reached (NULL); +} + +/** + * nm_dbus_manager_new_proxy: + * @self: the #NMDBusManager + * @connection: the GDBusConnection for which this connection should be created + * @proxy_type: the type of #GDBusProxy to create + * @name: any name on the message bus + * @path: name of the object instance to call methods on + * @iface: name of the interface to call methods on + * + * Creates a new proxy (of type @proxy_type) for a name on a given bus. Since + * the process which called the D-Bus method could be coming from a private + * connection or the system bus connection, different proxies must be created + * for each case. This function abstracts that. + * + * Returns: a #GDBusProxy capable of calling D-Bus methods of the calling process + */ +GDBusProxy * +nm_dbus_manager_new_proxy (NMDBusManager *self, + GDBusConnection *connection, + GType proxy_type, + const char *name, + const char *path, + const char *iface) +{ + const char *owner; + GDBusProxy *proxy; + GError *error = NULL; + + g_return_val_if_fail (g_type_is_a (proxy_type, G_TYPE_DBUS_PROXY), NULL); + g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), NULL); + + /* Might be a private connection, for which @name is fake */ + owner = nm_dbus_manager_connection_get_private_name (self, connection); + if (owner) { + g_return_val_if_fail (!g_strcmp0 (owner, name), NULL); + name = NULL; + } + + proxy = g_initable_new (proxy_type, NULL, &error, + "g-connection", connection, + "g-flags", (G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES | + G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS), + "g-name", name, + "g-object-path", path, + "g-interface-name", iface, + NULL); + if (!proxy) { + _LOGW ("could not create proxy for %s on connection %s: %s", + iface, name, error->message); + g_error_free (error); + } + return proxy; +} + +/*****************************************************************************/ + +GDBusConnection * +nm_dbus_manager_get_connection (NMDBusManager *self) +{ + g_return_val_if_fail (NM_IS_DBUS_MANAGER (self), NULL); + + return NM_DBUS_MANAGER_GET_PRIVATE (self)->connection; +} + +/*****************************************************************************/ + +static const NMDBusInterfaceInfoExtended * +_reg_data_get_interface_info (RegistrationData *reg_data) +{ + nm_assert (reg_data); + + return reg_data->klass->interface_infos[reg_data->info_idx]; +} + +/*****************************************************************************/ + +static void +dbus_vtable_method_call (GDBusConnection *connection, + const char *sender, + const char *object_path, + const char *interface_name, + const char *method_name, + GVariant *parameters, + GDBusMethodInvocation *invocation, + gpointer user_data) +{ + RegistrationData *reg_data = user_data; + NMDBusObject *obj = reg_data->obj; + const NMDBusInterfaceInfoExtended *interface_info = _reg_data_get_interface_info (reg_data); + const NMDBusMethodInfoExtended *method_info = NULL; + gboolean on_same_interface; + + on_same_interface = nm_streq (interface_info->parent.name, interface_name); + + /* handle property setter first... */ + if ( !on_same_interface + && nm_streq (interface_name, DBUS_INTERFACE_PROPERTIES) + && nm_streq (method_name, "Set")) { + NMDBusManager *self = nm_dbus_object_get_manager (obj); + NMDBusManagerPrivate *priv = NM_DBUS_MANAGER_GET_PRIVATE (self); + const NMDBusPropertyInfoExtended *property_info = NULL; + const char *property_interface; + const char *property_name; + gs_unref_variant GVariant *value = NULL; + + g_variant_get (parameters, "(&s&sv)", &property_interface, &property_name, &value); + + nm_assert (nm_streq (property_interface, interface_info->parent.name)); + + property_info = (const NMDBusPropertyInfoExtended *) nm_dbus_utils_interface_info_lookup_property (&interface_info->parent, + property_name, + NULL); + if ( !property_info + || !NM_FLAGS_HAS (property_info->parent.flags, G_DBUS_PROPERTY_INFO_FLAGS_WRITABLE)) + g_return_if_reached (); + + if (!priv->set_property_handler) { + g_dbus_method_invocation_return_error (invocation, + G_DBUS_ERROR, + G_DBUS_ERROR_AUTH_FAILED, + "Cannot authenticate setting property %s", + property_name); + return; + } + + priv->set_property_handler (obj, + interface_info, + property_info, + connection, + sender, + invocation, + value, + priv->set_property_handler_data); + return; + } + + if (on_same_interface) { + method_info = (const NMDBusMethodInfoExtended *) nm_dbus_utils_interface_info_lookup_method (&interface_info->parent, + method_name); + } + if (!method_info) { + g_dbus_method_invocation_return_error (invocation, + G_DBUS_ERROR, + G_DBUS_ERROR_UNKNOWN_METHOD, + "Unknown method %s", + method_name); + return; + } + + method_info->handle (reg_data->obj, + interface_info, + method_info, + connection, + sender, + invocation, + parameters); +} + +static GVariant * +_obj_get_property (RegistrationData *reg_data, + guint property_idx, + gboolean refetch) +{ + const NMDBusInterfaceInfoExtended *interface_info = _reg_data_get_interface_info (reg_data); + const NMDBusPropertyInfoExtended *property_info; + GVariant *value; + + property_info = (const NMDBusPropertyInfoExtended *) (interface_info->parent.properties[property_idx]); + + if (refetch) + nm_clear_g_variant (®_data->property_cache[property_idx].value); + else { + value = reg_data->property_cache[property_idx].value; + if (value) + goto out; + } + + value = nm_dbus_utils_get_property (G_OBJECT (reg_data->obj), + property_info->parent.signature, + property_info->property_name); + reg_data->property_cache[property_idx].value = value; +out: + return g_variant_ref (value); +} + +static GVariant * +dbus_vtable_get_property (GDBusConnection *connection, + const char *sender, + const char *object_path, + const char *interface_name, + const char *property_name, + GError **error, + gpointer user_data) +{ + RegistrationData *reg_data = user_data; + const NMDBusInterfaceInfoExtended *interface_info = _reg_data_get_interface_info (reg_data); + guint property_idx; + + if (!nm_dbus_utils_interface_info_lookup_property (&interface_info->parent, + property_name, + &property_idx)) + g_return_val_if_reached (NULL); + + return _obj_get_property (reg_data, property_idx, FALSE); +} + +static const GDBusInterfaceVTable dbus_vtable = { + .method_call = dbus_vtable_method_call, + .get_property = dbus_vtable_get_property, + + /* set_property is handled via method_call as well. We need to authenticate + * which requires an asynchronous handler. */ + .set_property = NULL, +}; + +static void +_obj_register (NMDBusManager *self, + NMDBusObject *obj) +{ + NMDBusManagerPrivate *priv = NM_DBUS_MANAGER_GET_PRIVATE (self); + guint i, k; + guint n_klasses; + GType gtype; + NMDBusObjectClass *klasses[10]; + const NMDBusInterfaceInfoExtended *const*prev_interface_infos = NULL; + GVariantBuilder builder; + + nm_assert (c_list_is_empty (&obj->internal.registration_lst_head)); + nm_assert (priv->connection); + + n_klasses = 0; + gtype = G_OBJECT_TYPE (obj); + while (gtype != NM_TYPE_DBUS_OBJECT) { + nm_assert (n_klasses < G_N_ELEMENTS (klasses)); + klasses[n_klasses++] = g_type_class_ref (gtype); + gtype = g_type_parent (gtype); + } + + for (k = n_klasses; k > 0; ) { + NMDBusObjectClass *klass = NM_DBUS_OBJECT_CLASS (klasses[--k]); + + if (!klass->interface_infos) + continue; + + if (prev_interface_infos == klass->interface_infos) { + /* derived classes inherrit the interface-infos from the parent class. + * For convenience, we allow the subclass to leave interface-infos untouched, + * but it means we must ignore the parent's interface, because we already + * handled it. + * + * Note that the loop goes from the parent classes to child classes */ + continue; + } + prev_interface_infos = klass->interface_infos; + + for (i = 0; klass->interface_infos[i]; i++) { + const NMDBusInterfaceInfoExtended *interface_info = klass->interface_infos[i]; + RegistrationData *reg_data; + gs_free_error GError *error = NULL; + guint registration_id; + guint prop_len = NM_PTRARRAY_LEN (interface_info->parent.properties); + + reg_data = g_malloc0 (sizeof (RegistrationData) + (sizeof (PropertyCacheData) * prop_len)); + + registration_id = g_dbus_connection_register_object (priv->connection, + obj->internal.path, + NM_UNCONST_PTR (GDBusInterfaceInfo, &interface_info->parent), + &dbus_vtable, + reg_data, + NULL, + &error); + if (!registration_id) { + _LOGE ("failure to register object %s: %s", obj->internal.path, error->message); + g_free (reg_data); + continue; + } + + reg_data->obj = obj; + reg_data->klass = g_type_class_ref (G_TYPE_FROM_CLASS (klass)); + reg_data->info_idx = i; + reg_data->registration_id = registration_id; + c_list_link_tail (&obj->internal.registration_lst_head, ®_data->registration_lst); + } + } + + for (k = 0; k < n_klasses; k++) + g_type_class_unref (klasses[k]); + + nm_assert (!c_list_is_empty (&obj->internal.registration_lst_head)); + + /* Currently the interfaces of an object do not changed and strictly depend on the object glib type. + * We don't need more flixibility, and it simplifies the code. Hence, now emit interface-added + * signal for the new object. + * + * Warning: note that if @obj's notify signal is currently blocked via g_object_freeze_notify(), + * we might emit properties with an inconsistent (internal) state. There is no easy solution, + * because we have to emit the signal now, and we don't know what the correct desired state + * of the properties is. + * Another problem is, upon unfreezing the signals, we immediately send PropertiesChanged + * notifications out. Which is a bit odd, as we just export the object. + * + * In general, it's ok to export an object with frozen signals. But you better make sure + * that all properties are in a self-consistent state when exporting the object. */ + g_dbus_connection_emit_signal (priv->connection, + NULL, + OBJECT_MANAGER_SERVER_BASE_PATH, + interface_info_objmgr.name, + signal_info_objmgr_interfaces_added.name, + g_variant_new ("(oa{sa{sv}})", + obj->internal.path, + _obj_collect_properties_all (obj, &builder)), + NULL); +} + +static void +_obj_unregister (NMDBusManager *self, + NMDBusObject *obj) +{ + NMDBusManagerPrivate *priv = NM_DBUS_MANAGER_GET_PRIVATE (self); + RegistrationData *reg_data; + GVariantBuilder builder; + + nm_assert (NM_IS_DBUS_OBJECT (obj)); + + if (!priv->connection) { + /* nothing to do for the moment. */ + nm_assert (c_list_is_empty (&obj->internal.registration_lst_head)); + return; + } + + nm_assert (!c_list_is_empty (&obj->internal.registration_lst_head)); + nm_assert (priv->objmgr_registration_id); + + g_variant_builder_init (&builder, G_VARIANT_TYPE ("as")); + + while ((reg_data = c_list_last_entry (&obj->internal.registration_lst_head, RegistrationData, registration_lst))) { + const NMDBusInterfaceInfoExtended *interface_info = _reg_data_get_interface_info (reg_data); + guint i; + + g_variant_builder_add (&builder, + "s", + interface_info->parent.name); + c_list_unlink_stale (®_data->registration_lst); + if (!g_dbus_connection_unregister_object (priv->connection, reg_data->registration_id)) + nm_assert_not_reached (); + + if (interface_info->parent.properties) { + for (i = 0; interface_info->parent.properties[i]; i++) + nm_clear_g_variant (®_data->property_cache[i].value); + } + + g_type_class_unref (reg_data->klass); + g_free (reg_data); + } + + g_dbus_connection_emit_signal (priv->connection, + NULL, + OBJECT_MANAGER_SERVER_BASE_PATH, + interface_info_objmgr.name, + signal_info_objmgr_interfaces_removed.name, + g_variant_new ("(oas)", + obj->internal.path, + &builder), + NULL); +} + +NMDBusObject * +nm_dbus_manager_lookup_object (NMDBusManager *self, const char *path) +{ + NMDBusManagerPrivate *priv; + gpointer ptr; + NMDBusObject *obj; + + g_return_val_if_fail (NM_IS_DBUS_MANAGER (self), NULL); + g_return_val_if_fail (path, NULL); + + priv = NM_DBUS_MANAGER_GET_PRIVATE (self); + + ptr = g_hash_table_lookup (priv->objects_by_path, &path); + if (!ptr) + return NULL; + + obj = (NMDBusObject *) (((char *) ptr) - G_STRUCT_OFFSET (NMDBusObject, internal)); + nm_assert (NM_IS_DBUS_OBJECT (obj)); + return obj; +} + +void +_nm_dbus_manager_obj_export (NMDBusObject *obj) +{ + NMDBusManager *self; + NMDBusManagerPrivate *priv; + + g_return_if_fail (NM_IS_DBUS_OBJECT (obj)); + g_return_if_fail (obj->internal.path); + g_return_if_fail (NM_IS_DBUS_MANAGER (obj->internal.bus_manager)); + g_return_if_fail (c_list_is_empty (&obj->internal.objects_lst)); + nm_assert (c_list_is_empty (&obj->internal.registration_lst_head)); + + self = obj->internal.bus_manager; + priv = NM_DBUS_MANAGER_GET_PRIVATE (self); + + if (!g_hash_table_add (priv->objects_by_path, &obj->internal)) + nm_assert_not_reached (); + c_list_link_tail (&priv->objects_lst_head, &obj->internal.objects_lst); + + if (priv->connection) + _obj_register (self, obj); +} + +void +_nm_dbus_manager_obj_unexport (NMDBusObject *obj) +{ + NMDBusManager *self; + NMDBusManagerPrivate *priv; + + g_return_if_fail (NM_IS_DBUS_OBJECT (obj)); + g_return_if_fail (obj->internal.path); + g_return_if_fail (NM_IS_DBUS_MANAGER (obj->internal.bus_manager)); + g_return_if_fail (!c_list_is_empty (&obj->internal.objects_lst)); + + self = obj->internal.bus_manager; + priv = NM_DBUS_MANAGER_GET_PRIVATE (self); + + nm_assert (&obj->internal == g_hash_table_lookup (priv->objects_by_path, &obj->internal)); + nm_assert (c_list_contains (&priv->objects_lst_head, &obj->internal.objects_lst)); + + _obj_unregister (self, obj); + + if (!g_hash_table_remove (priv->objects_by_path, &obj->internal)) + nm_assert_not_reached (); + c_list_unlink (&obj->internal.objects_lst); +} + +void +_nm_dbus_manager_obj_notify (NMDBusObject *obj, + guint n_pspecs, + const GParamSpec *const*pspecs) +{ + NMDBusManager *self; + NMDBusManagerPrivate *priv; + RegistrationData *reg_data; + guint i, p; + gboolean any_legacy_signals = FALSE; + gboolean any_legacy_properties = FALSE; + GVariantBuilder legacy_builder; + GVariant *device_statistics_args = NULL; + + nm_assert (NM_IS_DBUS_OBJECT (obj)); + nm_assert (obj->internal.path); + nm_assert (NM_IS_DBUS_MANAGER (obj->internal.bus_manager)); + nm_assert (!c_list_is_empty (&obj->internal.objects_lst)); + + c_list_for_each_entry (reg_data, &obj->internal.registration_lst_head, registration_lst) { + if (_reg_data_get_interface_info (reg_data)->legacy_property_changed) { + any_legacy_signals = TRUE; + break; + } + } + + self = obj->internal.bus_manager; + priv = NM_DBUS_MANAGER_GET_PRIVATE (self); + + /* do a naive search for the matching NMDBusPropertyInfoExtended infos. Since the number of + * (interaces x properties) is static and possibly small, this naive search is effectively + * O(1). We might wanna introduce some index to lookup the properties in question faster. + * + * The nice part of this implementation is however, that the order in which properties + * are added to the GVariant is strictly defined to be the order in which the D-Bus property-info + * is declared. Getting a defined ordering with some smart lookup would be hard. */ + c_list_for_each_entry (reg_data, &obj->internal.registration_lst_head, registration_lst) { + const NMDBusInterfaceInfoExtended *interface_info = _reg_data_get_interface_info (reg_data); + gboolean has_properties = FALSE; + GVariantBuilder builder; + GVariantBuilder invalidated_builder; + GVariant *args; + + if (!interface_info->parent.properties) + continue; + + for (i = 0; interface_info->parent.properties[i]; i++) { + const NMDBusPropertyInfoExtended *property_info = (const NMDBusPropertyInfoExtended *) interface_info->parent.properties[i]; + + for (p = 0; p < n_pspecs; p++) { + const GParamSpec *pspec = pspecs[p]; + gs_unref_variant GVariant *value = NULL; + + if (!nm_streq (property_info->property_name, pspec->name)) + continue; + + value = _obj_get_property (reg_data, i, TRUE); + + if ( property_info->include_in_legacy_property_changed + && any_legacy_signals) { + /* also track the value in the legacy_builder to emit legacy signals below. */ + if (!any_legacy_properties) { + any_legacy_properties = TRUE; + g_variant_builder_init (&legacy_builder, G_VARIANT_TYPE ("a{sv}")); + } + g_variant_builder_add (&legacy_builder, "{sv}", property_info->parent.name, value); + } + + if (!has_properties) { + has_properties = TRUE; + g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{sv}")); + } + g_variant_builder_add (&builder, "{sv}", property_info->parent.name, value); + } + } + + if (!has_properties) + continue; + + args = g_variant_builder_end (&builder); + + if (G_UNLIKELY (interface_info == &nm_interface_info_device_statistics)) { + /* we treat the Device.Statistics signal special, because we need to + * emit a signal also for it (below). */ + nm_assert (!device_statistics_args); + device_statistics_args = g_variant_ref_sink (args); + } + + g_variant_builder_init (&invalidated_builder, G_VARIANT_TYPE ("as")); + g_dbus_connection_emit_signal (priv->connection, + NULL, + obj->internal.path, + "org.freedesktop.DBus.Properties", + "PropertiesChanged", + g_variant_new ("(s@a{sv}as)", + interface_info->parent.name, + args, + &invalidated_builder), + NULL); + } + + if (G_UNLIKELY (device_statistics_args)) { + /* this is a special interface: it has a legacy PropertiesChanged signal, + * however, contrary to other interfaces with ~regular~ legacy signals, + * we only notify about properties that actually belong to this interface. */ + g_dbus_connection_emit_signal (priv->connection, + NULL, + obj->internal.path, + nm_interface_info_device_statistics.parent.name, + "PropertiesChanged", + g_variant_new ("(@a{sv})", + device_statistics_args), + NULL); + g_variant_unref (device_statistics_args); + } + + if (any_legacy_properties) { + gs_unref_variant GVariant *args = NULL; + + /* The legacy PropertyChanged signal on the NetworkManager D-Bus interface is + * deprecated for the standard signal on org.freedesktop.DBus.Properties. However, + * for backward compatibility, we still need to emit it. + * + * Due to a bug in dbus-glib in NetworkManager <= 1.0, the signal would + * not only notify about properties that were actually on the corresponding + * D-Bus interface. Instead, it would notify about all relevant properties + * on all interfaces that had such a signal. + * + * For example, "HwAddress" gets emitted both on "fdo.NM.Device.Ethernet" + * and "fdo.NM.Device.Veth" for veth interfaces, although only the former + * actually has such a property. + * Also note that "fdo.NM.Device" interface has no legacy signal. All notifications + * about its properties are instead emitted on the interfaces of the subtypes. + * + * See bgo#770629 and commit bef26a2e69f51259095fa080221db73de09fd38d. + */ + args = g_variant_ref_sink (g_variant_new ("(a{sv})", + &legacy_builder)); + c_list_for_each_entry (reg_data, &obj->internal.registration_lst_head, registration_lst) { + const NMDBusInterfaceInfoExtended *interface_info = _reg_data_get_interface_info (reg_data); + + if (interface_info->legacy_property_changed) { + g_dbus_connection_emit_signal (priv->connection, + NULL, + obj->internal.path, + interface_info->parent.name, + "PropertiesChanged", + args, + NULL); + } + } + } +} + +void +_nm_dbus_manager_obj_emit_signal (NMDBusObject *obj, + const NMDBusInterfaceInfoExtended *interface_info, + const GDBusSignalInfo *signal_info, + GVariant *args) +{ + NMDBusManager *self; + NMDBusManagerPrivate *priv; + + g_return_if_fail (NM_IS_DBUS_OBJECT (obj)); + g_return_if_fail (obj->internal.path); + g_return_if_fail (NM_IS_DBUS_MANAGER (obj->internal.bus_manager)); + g_return_if_fail (!c_list_is_empty (&obj->internal.objects_lst)); + + self = obj->internal.bus_manager; + priv = NM_DBUS_MANAGER_GET_PRIVATE (self); + + if (!priv->connection) { + nm_g_variant_unref_floating (args); + return; + } + + g_dbus_connection_emit_signal (priv->connection, + NULL, + obj->internal.path, + interface_info->parent.name, + signal_info->name, + args, + NULL); +} + +/*****************************************************************************/ + +static GVariantBuilder * +_obj_collect_properties_per_interface (NMDBusObject *obj, + RegistrationData *reg_data, + GVariantBuilder *builder) +{ + const NMDBusInterfaceInfoExtended *interface_info = _reg_data_get_interface_info (reg_data); + guint i; + + g_variant_builder_init (builder, G_VARIANT_TYPE ("a{sv}")); + if (interface_info->parent.properties) { + for (i = 0; interface_info->parent.properties[i]; i++) { + const NMDBusPropertyInfoExtended *property_info = (const NMDBusPropertyInfoExtended *) interface_info->parent.properties[i]; + gs_unref_variant GVariant *variant = NULL; + + variant = _obj_get_property (reg_data, i, FALSE); + g_variant_builder_add (builder, + "{sv}", + property_info->parent.name, + variant); + } + } + return builder; +} + +static GVariantBuilder * +_obj_collect_properties_all (NMDBusObject *obj, + GVariantBuilder *builder) +{ + RegistrationData *reg_data; + + g_variant_builder_init (builder, G_VARIANT_TYPE ("a{sa{sv}}")); + + c_list_for_each_entry (reg_data, &obj->internal.registration_lst_head, registration_lst) { + GVariantBuilder properties_builder; + + g_variant_builder_add (builder, + "{sa{sv}}", + _reg_data_get_interface_info (reg_data)->parent.name, + _obj_collect_properties_per_interface (obj, + reg_data, + &properties_builder)); + } + + return builder; +} + +static void +dbus_vtable_objmgr_method_call (GDBusConnection *connection, + const char *sender, + const char *object_path, + const char *interface_name, + const char *method_name, + GVariant *parameters, + GDBusMethodInvocation *invocation, + gpointer user_data) +{ + NMDBusManager *self = user_data; + NMDBusManagerPrivate *priv = NM_DBUS_MANAGER_GET_PRIVATE (self); + GVariantBuilder array_builder; + NMDBusObject *obj; + + nm_assert (nm_streq0 (object_path, OBJECT_MANAGER_SERVER_BASE_PATH)); + + if ( !nm_streq (method_name, "GetManagedObjects") + || !nm_streq (interface_name, interface_info_objmgr.name)) { + g_dbus_method_invocation_return_error (invocation, + G_DBUS_ERROR, + G_DBUS_ERROR_UNKNOWN_METHOD, + "Unknown method %s - only GetManagedObjects() is supported", + method_name); + return; + } + + g_variant_builder_init (&array_builder, G_VARIANT_TYPE ("a{oa{sa{sv}}}")); + c_list_for_each_entry (obj, &priv->objects_lst_head, internal.objects_lst) { + GVariantBuilder interfaces_builder; + + /* note that we are called on an idle handler. Hence, all properties are + * supposed to be in a consistent state. That is true, if you always + * g_object_thaw_notify() before returning to the mainloop. Keeping + * signals frozen between while returning from the current call stack + * is anyway a very fragile thing, easy to get wrong. Don't do that. */ + g_variant_builder_add (&array_builder, + "{oa{sa{sv}}}", + obj->internal.path, + _obj_collect_properties_all (obj, + &interfaces_builder)); + } + g_dbus_method_invocation_return_value (invocation, + g_variant_new ("(a{oa{sa{sv}}})", + &array_builder)); +} + +static const GDBusInterfaceVTable dbus_vtable_objmgr = { + .method_call = dbus_vtable_objmgr_method_call +}; + +static const GDBusSignalInfo signal_info_objmgr_interfaces_added = NM_DEFINE_GDBUS_SIGNAL_INFO_INIT ( + "InterfacesAdded", + .args = NM_DEFINE_GDBUS_ARG_INFOS ( + NM_DEFINE_GDBUS_ARG_INFO ("object_path", "o"), + NM_DEFINE_GDBUS_ARG_INFO ("interfaces_and_properties", "a{sa{sv}}"), + ), +); + +static const GDBusSignalInfo signal_info_objmgr_interfaces_removed = NM_DEFINE_GDBUS_SIGNAL_INFO_INIT ( + "InterfacesRemoved", + .args = NM_DEFINE_GDBUS_ARG_INFOS ( + NM_DEFINE_GDBUS_ARG_INFO ("object_path", "o"), + NM_DEFINE_GDBUS_ARG_INFO ("interfaces", "as"), + ), +); + +static const GDBusInterfaceInfo interface_info_objmgr = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT ( + "org.freedesktop.DBus.ObjectManager", + .methods = NM_DEFINE_GDBUS_METHOD_INFOS ( + NM_DEFINE_GDBUS_METHOD_INFO ( + "GetManagedObjects", + .out_args = NM_DEFINE_GDBUS_ARG_INFOS ( + NM_DEFINE_GDBUS_ARG_INFO ("object_paths_interfaces_and_properties", "a{oa{sa{sv}}}"), + ), + ), + ), + .signals = NM_DEFINE_GDBUS_SIGNAL_INFOS ( + &signal_info_objmgr_interfaces_added, + &signal_info_objmgr_interfaces_removed, + ), +); + +/*****************************************************************************/ + +gboolean +nm_dbus_manager_start (NMDBusManager *self, + NMDBusManagerSetPropertyHandler set_property_handler, + gpointer set_property_handler_data) +{ + NMDBusManagerPrivate *priv; + gs_free_error GError *error = NULL; + gs_unref_variant GVariant *ret = NULL; + gs_unref_object GDBusConnection *connection = NULL; + gs_unref_object GDBusProxy *proxy = NULL; + guint32 result; + guint registration_id; + NMDBusObject *obj; + + g_return_val_if_fail (NM_IS_DBUS_MANAGER (self), FALSE); + + priv = NM_DBUS_MANAGER_GET_PRIVATE (self); + + priv->set_property_handler = set_property_handler; + priv->set_property_handler_data = set_property_handler_data; + + g_return_val_if_fail (!priv->connection, FALSE); + + /* we will create the D-Bus connection and registering the name synchronously. + * The reason why that is necessary is because: + * (1) if we are unable to create a D-Bus connection, it means D-Bus is not + * available and we run in D-Bus less mode. We do not support creating + * a D-Bus connection later on. This disconnected mode is useful for initrd + * (well, currently not yet, but will be). + * (2) if we are able to create the connection and register the name, + * all is good and we run with D-Bus. Note that D-Bus disconnects + * from D-Bus are ignored. Essentially, we do not support restarting + * D-Bus. + * (3) if we are able to create the connection but registration fails, + * it means that something is borked. Quite possibly another NetworkManager + * instance is running. We need to exit right away. + * To appease (1) and (3), we cannot initalize synchronously, because we need + * to know right away whether another NetworkManager instance is running (3). + **/ + + connection = g_bus_get_sync (G_BUS_TYPE_SYSTEM, + NULL, + &error); + if (!connection) { + _LOGI ("cannot connect to D-Bus and proceed without (%s)", error->message); + return TRUE; + } + + g_dbus_connection_set_exit_on_close (connection, FALSE); + + proxy = g_dbus_proxy_new_sync (connection, + G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES + | G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS, + NULL, + DBUS_SERVICE_DBUS, + DBUS_PATH_DBUS, + DBUS_INTERFACE_DBUS, + NULL, + &error); + if (!proxy) { + _LOGE ("fatal failure to initialize D-Bus: %s", error->message); + return FALSE; + } + + ret = _nm_dbus_proxy_call_sync (proxy, + "RequestName", + g_variant_new ("(su)", + NM_DBUS_SERVICE, + DBUS_NAME_FLAG_DO_NOT_QUEUE), + G_VARIANT_TYPE ("(u)"), + G_DBUS_CALL_FLAGS_NONE, -1, + NULL, + &error); + if (!ret) { + _LOGE ("fatal failure to aquire D-Bus service \"%s"": %s", + NM_DBUS_SERVICE, error->message); + return FALSE; + } + + g_variant_get (ret, "(u)", &result); + if (result != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) { + _LOGE ("fatal failure to acquire D-Bus service \"%s\" (%u). Service already taken", + NM_DBUS_SERVICE, (guint) result); + return FALSE; + } + + registration_id = g_dbus_connection_register_object (connection, + OBJECT_MANAGER_SERVER_BASE_PATH, + NM_UNCONST_PTR (GDBusInterfaceInfo, &interface_info_objmgr), + &dbus_vtable_objmgr, + self, + NULL, + &error); + if (!registration_id) { + _LOGE ("failure to register object manager: %s", error->message); + return FALSE; + } + + priv->objmgr_registration_id = registration_id; + priv->connection = g_steal_pointer (&connection); + priv->proxy = g_steal_pointer (&proxy); + + _LOGI ("aquired D-Bus service \"%s\"", NM_DBUS_SERVICE); + + c_list_for_each_entry (obj, &priv->objects_lst_head, internal.objects_lst) + _obj_register (self, obj); + + return TRUE; +} + +/*****************************************************************************/ + +static void +nm_dbus_manager_init (NMDBusManager *self) +{ + NMDBusManagerPrivate *priv = NM_DBUS_MANAGER_GET_PRIVATE (self); + + c_list_init (&priv->private_servers_lst_head); + c_list_init (&priv->objects_lst_head); + priv->objects_by_path = g_hash_table_new ((GHashFunc) _objects_by_path_hash, (GEqualFunc) _objects_by_path_equal); +} + +static void +dispose (GObject *object) +{ + NMDBusManager *self = NM_DBUS_MANAGER (object); + NMDBusManagerPrivate *priv = NM_DBUS_MANAGER_GET_PRIVATE (self); + PrivateServer *s, *s_safe; + + /* All exported NMDBusObject instances keep the manager alive, so we don't + * expect any remaining objects. */ + nm_assert (!priv->objects_by_path || g_hash_table_size (priv->objects_by_path) == 0); + nm_assert (c_list_is_empty (&priv->objects_lst_head)); + + g_clear_pointer (&priv->objects_by_path, g_hash_table_destroy); + + c_list_for_each_entry_safe (s, s_safe, &priv->private_servers_lst_head, private_servers_lst) + private_server_free (s); + + if (priv->objmgr_registration_id) { + g_dbus_connection_unregister_object (priv->connection, + nm_steal_int (&priv->objmgr_registration_id)); + } + + g_clear_object (&priv->proxy); + g_clear_object (&priv->connection); + + G_OBJECT_CLASS (nm_dbus_manager_parent_class)->dispose (object); +} + +static void +nm_dbus_manager_class_init (NMDBusManagerClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->dispose = dispose; + + signals[PRIVATE_CONNECTION_NEW] = + g_signal_new (NM_DBUS_MANAGER_PRIVATE_CONNECTION_NEW, + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED, + 0, NULL, NULL, NULL, + G_TYPE_NONE, 2, G_TYPE_DBUS_CONNECTION, G_TYPE_DBUS_OBJECT_MANAGER_SERVER); + + signals[PRIVATE_CONNECTION_DISCONNECTED] = + g_signal_new (NM_DBUS_MANAGER_PRIVATE_CONNECTION_DISCONNECTED, + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED, + 0, NULL, NULL, NULL, + G_TYPE_NONE, 1, G_TYPE_POINTER); +} |