mirror of
https://gitlab.freedesktop.org/NetworkManager/NetworkManager.git
synced 2026-01-04 01:40:16 +01:00
We also do this for libnm, where it causes visible changes in behavior. But if somebody would rely on the hashing implementation for hash tables, it would be seriously flawed.
1055 lines
34 KiB
C
1055 lines
34 KiB
C
/* -*- 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 2014-2016 Red Hat, Inc.
|
|
*/
|
|
|
|
#include "nm-default.h"
|
|
|
|
#include "nm-exported-object.h"
|
|
|
|
#include <stdarg.h>
|
|
#include <string.h>
|
|
|
|
#include "nm-bus-manager.h"
|
|
|
|
#include "devices/nm-device.h"
|
|
#include "nm-active-connection.h"
|
|
#include "introspection/org.freedesktop.NetworkManager.Device.Statistics.h"
|
|
|
|
#if NM_MORE_ASSERTS >= 2
|
|
#define _ASSERT_NO_EARLY_EXPORT
|
|
#endif
|
|
|
|
/*****************************************************************************/
|
|
|
|
static gboolean quitting = FALSE;
|
|
|
|
/*****************************************************************************/
|
|
|
|
NM_GOBJECT_PROPERTIES_DEFINE (NMExportedObject,
|
|
PROP_PATH,
|
|
);
|
|
|
|
typedef struct {
|
|
GDBusInterfaceSkeleton *interface;
|
|
guint property_changed_signal_id;
|
|
GHashTable *pending_notifies;
|
|
} InterfaceData;
|
|
|
|
typedef struct _NMExportedObjectPrivate {
|
|
NMBusManager *bus_mgr;
|
|
char *path;
|
|
|
|
InterfaceData *interfaces;
|
|
guint num_interfaces;
|
|
|
|
guint notify_idle_id;
|
|
|
|
#ifdef _ASSERT_NO_EARLY_EXPORT
|
|
bool _constructed:1;
|
|
#endif
|
|
} NMExportedObjectPrivate;
|
|
|
|
G_DEFINE_ABSTRACT_TYPE (NMExportedObject, nm_exported_object, G_TYPE_DBUS_OBJECT_SKELETON);
|
|
|
|
#define NM_EXPORTED_OBJECT_GET_PRIVATE(self) _NM_GET_PRIVATE_PTR (self, NMExportedObject, NM_IS_EXPORTED_OBJECT)
|
|
|
|
/*****************************************************************************/
|
|
|
|
typedef struct {
|
|
GHashTable *properties;
|
|
GSList *skeleton_types;
|
|
GArray *methods;
|
|
} NMExportedObjectClassInfo;
|
|
|
|
static NM_CACHED_QUARK_FCN ("NMExportedObjectClassInfo", nm_exported_object_class_info_quark)
|
|
|
|
/*****************************************************************************/
|
|
|
|
#define _NMLOG_DOMAIN LOGD_CORE
|
|
#define _NMLOG(level, ...) __NMLOG_DEFAULT_WITH_ADDR (level, _NMLOG_DOMAIN, "exported-object", __VA_ARGS__)
|
|
|
|
#define _NMLOG2_DOMAIN LOGD_DBUS_PROPS
|
|
#define _NMLOG2(level, ...) __NMLOG_DEFAULT_WITH_ADDR (level, _NMLOG2_DOMAIN, "properties-changed", __VA_ARGS__)
|
|
|
|
/*****************************************************************************/
|
|
|
|
/* "AddConnectionUnsaved" -> "handle-add-connection-unsaved" */
|
|
char *
|
|
nm_exported_object_skeletonify_method_name (const char *dbus_method_name)
|
|
{
|
|
GString *out;
|
|
const char *p;
|
|
|
|
out = g_string_new ("handle");
|
|
for (p = dbus_method_name; *p; p++) {
|
|
if (g_ascii_isupper (*p) || p == dbus_method_name) {
|
|
g_string_append_c (out, '-');
|
|
g_string_append_c (out, g_ascii_tolower (*p));
|
|
} else
|
|
g_string_append_c (out, *p);
|
|
}
|
|
|
|
return g_string_free (out, FALSE);
|
|
}
|
|
|
|
/* "can-modify" -> "CanModify" */
|
|
static char *
|
|
dbusify_name (const char *gobject_name)
|
|
{
|
|
GString *out;
|
|
const char *p;
|
|
gboolean capitalize = TRUE;
|
|
|
|
out = g_string_new ("");
|
|
for (p = gobject_name; *p; p++) {
|
|
if (capitalize) {
|
|
g_string_append_c (out, g_ascii_toupper (*p));
|
|
capitalize = FALSE;
|
|
} else if (*p == '-')
|
|
capitalize = TRUE;
|
|
else
|
|
g_string_append_c (out, *p);
|
|
}
|
|
|
|
return g_string_free (out, FALSE);
|
|
}
|
|
|
|
/* "can_modify" -> "can-modify". Returns %NULL if @gobject_name contains no underscores */
|
|
static char *
|
|
hyphenify_name (const char *gobject_name)
|
|
{
|
|
char *hyphen_name, *p;
|
|
|
|
if (!strchr (gobject_name, '_'))
|
|
return NULL;
|
|
|
|
hyphen_name = g_strdup (gobject_name);
|
|
for (p = hyphen_name; *p; p++) {
|
|
if (*p == '_')
|
|
*p = '-';
|
|
}
|
|
return hyphen_name;
|
|
}
|
|
|
|
/* Called when an #NMExportedObject emits a signal that corresponds to a D-Bus
|
|
* signal, and re-emits that signal on the correct skeleton object as well.
|
|
*/
|
|
static gboolean
|
|
nm_exported_object_signal_hook (GSignalInvocationHint *ihint,
|
|
guint n_param_values,
|
|
const GValue *param_values,
|
|
gpointer data)
|
|
{
|
|
NMExportedObject *self = g_value_get_object (¶m_values[0]);
|
|
NMExportedObjectPrivate *priv;
|
|
GSignalQuery *signal_info = data;
|
|
GDBusInterfaceSkeleton *interface = NULL;
|
|
GValue *dbus_param_values;
|
|
guint i;
|
|
|
|
priv = NM_EXPORTED_OBJECT_GET_PRIVATE (self);
|
|
if (!priv->path)
|
|
return TRUE;
|
|
|
|
for (i = 0; i < priv->num_interfaces; i++) {
|
|
InterfaceData *ifdata = &priv->interfaces[i];
|
|
|
|
if (g_type_is_a (G_OBJECT_TYPE (ifdata->interface), signal_info->itype)) {
|
|
interface = ifdata->interface;
|
|
break;
|
|
}
|
|
}
|
|
g_return_val_if_fail (interface != NULL, TRUE);
|
|
|
|
dbus_param_values = g_newa (GValue, n_param_values);
|
|
memset (dbus_param_values, 0, sizeof (GValue) * n_param_values);
|
|
g_value_init (&dbus_param_values[0], G_OBJECT_TYPE (interface));
|
|
g_value_set_object (&dbus_param_values[0], interface);
|
|
for (i = 1; i < n_param_values; i++) {
|
|
if (g_type_is_a (param_values[i].g_type, NM_TYPE_EXPORTED_OBJECT)) {
|
|
NMExportedObject *arg = g_value_get_object (¶m_values[i]);
|
|
|
|
g_value_init (&dbus_param_values[i], G_TYPE_STRING);
|
|
if (arg && nm_exported_object_is_exported (arg))
|
|
g_value_set_string (&dbus_param_values[i], nm_exported_object_get_path (arg));
|
|
else
|
|
g_value_set_string (&dbus_param_values[i], "/");
|
|
} else {
|
|
g_value_init (&dbus_param_values[i], param_values[i].g_type);
|
|
g_value_copy (¶m_values[i], &dbus_param_values[i]);
|
|
}
|
|
}
|
|
|
|
g_signal_emitv (dbus_param_values, signal_info->signal_id, 0, NULL);
|
|
|
|
for (i = 0; i < n_param_values; i++)
|
|
g_value_unset (&dbus_param_values[i]);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* nm_exported_object_class_add_interface:
|
|
* @object_class: an #NMExportedObjectClass
|
|
* @dbus_skeleton_type: the type of the #GDBusInterfaceSkeleton to add
|
|
* @...: method name / handler pairs, %NULL-terminated
|
|
*
|
|
* Adds @dbus_skeleton_type to the list of D-Bus interfaces implemented by
|
|
* @object_class. Instances of @object_class will automatically have a skeleton
|
|
* of that type created, which will be exported when you call
|
|
* nm_exported_object_export().
|
|
*
|
|
* The skeleton's properties will be initialized from the #NMExportedObject's,
|
|
* and bidirectional bindings will be set up between them. When exported
|
|
* properties change, both the org.freedesktop.DBus.Properties.PropertiesChanged
|
|
* signal and the traditional NetworkManager PropertiesChanged signal will be
|
|
* emitted.
|
|
*
|
|
* When a signal is emitted on an #NMExportedObject that has the same name as a
|
|
* signal on @dbus_skeleton_type, it will automatically be emitted on the
|
|
* skeleton as well; #NMExportedObject arguments in the signal will be converted
|
|
* to D-Bus object paths in the skeleton signal.
|
|
*
|
|
* The arguments after @dbus_skeleton_type are pairs of D-Bus method names (in
|
|
* CamelCase), and the corresponding handlers for them (which must have the same
|
|
* prototype as the corresponding "handle-..." signal on @dbus_skeleton_type,
|
|
* except with no return value, and with the first argument being an object of
|
|
* @object_class's type, not of @dbus_skeleton_type).
|
|
*
|
|
* It is a programmer error if:
|
|
* - @object_class does not define a property of the same name and type as
|
|
* each of @dbus_skeleton_type's properties.
|
|
* - @object_class does not define a signal with the same name and arguments
|
|
* as each of @dbus_skeleton_type's signals.
|
|
* - the list of method names includes any names that do not correspond to
|
|
* "handle-" signals on @dbus_skeleton_type.
|
|
* - the list of method names does not include every method defined by
|
|
* @dbus_skeleton_type.
|
|
*/
|
|
void
|
|
nm_exported_object_class_add_interface (NMExportedObjectClass *object_class,
|
|
GType dbus_skeleton_type,
|
|
...)
|
|
{
|
|
NMExportedObjectClassInfo *classinfo;
|
|
NMExportedObjectDBusMethodImpl method;
|
|
va_list ap;
|
|
const char *method_name;
|
|
GCallback impl;
|
|
gs_free GType *interfaces = NULL;
|
|
guint n_interfaces;
|
|
guint n_signals, n_method_signals;
|
|
guint object_signal_id;
|
|
GSignalQuery query;
|
|
int i, s;
|
|
GObjectClass *dbus_object_class;
|
|
gs_free GParamSpec **dbus_properties = NULL;
|
|
GParamSpec *object_property;
|
|
guint n_dbus_properties;
|
|
|
|
g_return_if_fail (NM_IS_EXPORTED_OBJECT_CLASS (object_class));
|
|
g_return_if_fail (g_type_is_a (dbus_skeleton_type, G_TYPE_DBUS_INTERFACE_SKELETON));
|
|
|
|
classinfo = g_type_get_qdata (G_TYPE_FROM_CLASS (object_class),
|
|
nm_exported_object_class_info_quark ());
|
|
if (!classinfo) {
|
|
classinfo = g_slice_new (NMExportedObjectClassInfo);
|
|
classinfo->skeleton_types = NULL;
|
|
classinfo->methods = g_array_new (FALSE, FALSE, sizeof (NMExportedObjectDBusMethodImpl));
|
|
classinfo->properties = g_hash_table_new (nm_str_hash, g_str_equal);
|
|
g_type_set_qdata (G_TYPE_FROM_CLASS (object_class),
|
|
nm_exported_object_class_info_quark (), classinfo);
|
|
}
|
|
|
|
classinfo->skeleton_types = g_slist_prepend (classinfo->skeleton_types,
|
|
GSIZE_TO_POINTER (dbus_skeleton_type));
|
|
|
|
/* Ensure @dbus_skeleton_type's class_init has run, so its signals/properties
|
|
* will be defined.
|
|
*/
|
|
dbus_object_class = g_type_class_ref (dbus_skeleton_type);
|
|
|
|
/* Add method implementations from the varargs */
|
|
va_start (ap, dbus_skeleton_type);
|
|
while ((method_name = va_arg (ap, const char *)) && (impl = va_arg (ap, GCallback))) {
|
|
method.dbus_skeleton_type = dbus_skeleton_type;
|
|
method.method_name = nm_exported_object_skeletonify_method_name (method_name);
|
|
g_assert (g_signal_lookup (method.method_name, dbus_skeleton_type) != 0);
|
|
method.impl = impl;
|
|
|
|
g_array_append_val (classinfo->methods, method);
|
|
}
|
|
va_end (ap);
|
|
|
|
/* Properties */
|
|
dbus_properties = g_object_class_list_properties (dbus_object_class, &n_dbus_properties);
|
|
for (i = 0; i < n_dbus_properties; i++) {
|
|
char *hyphen_name;
|
|
|
|
if (g_str_has_prefix (dbus_properties[i]->name, "g-"))
|
|
continue;
|
|
|
|
object_property = g_object_class_find_property (G_OBJECT_CLASS (object_class),
|
|
dbus_properties[i]->name);
|
|
g_assert (object_property != NULL);
|
|
g_assert (object_property->value_type == dbus_properties[i]->value_type);
|
|
|
|
g_assert (!g_hash_table_contains (classinfo->properties, dbus_properties[i]->name));
|
|
g_hash_table_insert (classinfo->properties,
|
|
g_strdup (dbus_properties[i]->name),
|
|
dbusify_name (dbus_properties[i]->name));
|
|
hyphen_name = hyphenify_name (dbus_properties[i]->name);
|
|
if (hyphen_name) {
|
|
g_assert (!g_hash_table_contains (classinfo->properties, hyphen_name));
|
|
g_hash_table_insert (classinfo->properties,
|
|
hyphen_name,
|
|
dbusify_name (dbus_properties[i]->name));
|
|
}
|
|
}
|
|
|
|
/* Signals. Unlike g_object_class_list_properties(), g_signal_list_ids() is
|
|
* "shallow", so we need to query each implemented gdbus-generated interface
|
|
* separately.
|
|
*/
|
|
interfaces = g_type_interfaces (dbus_skeleton_type, &n_interfaces);
|
|
n_method_signals = 0;
|
|
for (i = 0; i < n_interfaces; i++) {
|
|
gs_free guint *dbus_signals = NULL;
|
|
|
|
dbus_signals = g_signal_list_ids (interfaces[i], &n_signals);
|
|
for (s = 0; s < n_signals; s++) {
|
|
g_signal_query (dbus_signals[s], &query);
|
|
|
|
/* PropertiesChanged is handled specially */
|
|
if (!strcmp (query.signal_name, "properties-changed"))
|
|
continue;
|
|
|
|
if (g_str_has_prefix (query.signal_name, "handle-")) {
|
|
n_method_signals++;
|
|
continue;
|
|
}
|
|
|
|
object_signal_id = g_signal_lookup (query.signal_name, G_TYPE_FROM_CLASS (object_class));
|
|
g_assert (object_signal_id != 0);
|
|
|
|
g_signal_add_emission_hook (object_signal_id, 0,
|
|
nm_exported_object_signal_hook,
|
|
g_memdup (&query, sizeof (query)),
|
|
g_free);
|
|
}
|
|
}
|
|
|
|
g_type_class_unref (dbus_object_class);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
/* "meta-marshaller" that receives the skeleton "handle-foo" signal, replaces
|
|
* the skeleton object with an #NMExportedObject in the parameters, drops the
|
|
* user_data parameter, and adds a "TRUE" return value (indicating to gdbus that
|
|
* the signal was handled).
|
|
*/
|
|
static void
|
|
nm_exported_object_meta_marshal (GClosure *closure, GValue *return_value,
|
|
guint n_param_values, const GValue *param_values,
|
|
gpointer invocation_hint, gpointer marshal_data)
|
|
{
|
|
GValue *local_param_values;
|
|
|
|
local_param_values = g_new0 (GValue, n_param_values);
|
|
g_value_init (&local_param_values[0], G_TYPE_POINTER);
|
|
g_value_set_pointer (&local_param_values[0], closure->data);
|
|
memcpy (local_param_values + 1, param_values + 1, (n_param_values - 1) * sizeof (GValue));
|
|
|
|
g_cclosure_marshal_generic (closure, NULL,
|
|
n_param_values, local_param_values,
|
|
invocation_hint,
|
|
((GCClosure *)closure)->callback);
|
|
g_value_set_boolean (return_value, TRUE);
|
|
|
|
g_value_unset (&local_param_values[0]);
|
|
g_free (local_param_values);
|
|
}
|
|
|
|
static NM_CACHED_QUARK_FCN ("skeleton-data", _skeleton_data_quark)
|
|
|
|
typedef struct {
|
|
GBinding **prop_bindings;
|
|
gulong *method_signals;
|
|
} SkeletonData;
|
|
|
|
GDBusInterfaceSkeleton *
|
|
nm_exported_object_skeleton_create (GType dbus_skeleton_type,
|
|
GObjectClass *object_class,
|
|
const NMExportedObjectDBusMethodImpl *methods,
|
|
guint methods_len,
|
|
GObject *target)
|
|
{
|
|
GDBusInterfaceSkeleton *interface;
|
|
gs_free GParamSpec **properties = NULL;
|
|
SkeletonData *skeleton_data;
|
|
guint n_properties;
|
|
guint i, j;
|
|
|
|
interface = G_DBUS_INTERFACE_SKELETON (g_object_new (dbus_skeleton_type, NULL));
|
|
|
|
skeleton_data = g_slice_new (SkeletonData);
|
|
|
|
/* Bind properties */
|
|
properties = g_object_class_list_properties (G_OBJECT_GET_CLASS (interface), &n_properties);
|
|
skeleton_data->prop_bindings = g_new (GBinding *, n_properties + 1);
|
|
for (i = 0, j = 0; i < n_properties; i++) {
|
|
GParamSpec *nm_property;
|
|
GBindingFlags flags;
|
|
GBinding *prop_binding;
|
|
|
|
nm_property = g_object_class_find_property (object_class, properties[i]->name);
|
|
if (!nm_property)
|
|
continue;
|
|
|
|
flags = G_BINDING_SYNC_CREATE;
|
|
if ( (nm_property->flags & G_PARAM_WRITABLE)
|
|
&& !(nm_property->flags & G_PARAM_CONSTRUCT_ONLY))
|
|
flags |= G_BINDING_BIDIRECTIONAL;
|
|
prop_binding = g_object_bind_property (target, properties[i]->name,
|
|
interface, properties[i]->name,
|
|
flags);
|
|
if (prop_binding)
|
|
skeleton_data->prop_bindings[j++] = prop_binding;
|
|
}
|
|
skeleton_data->prop_bindings[j++] = NULL;
|
|
|
|
/* Bind methods */
|
|
skeleton_data->method_signals = g_new (gulong, methods_len + 1);
|
|
for (i = 0, j = 0; i < methods_len; i++) {
|
|
const NMExportedObjectDBusMethodImpl *method = &methods[i];
|
|
GClosure *closure;
|
|
gulong method_signal;
|
|
|
|
/* ignore methods that are for a different skeleton-type. */
|
|
if ( method->dbus_skeleton_type
|
|
&& method->dbus_skeleton_type != dbus_skeleton_type)
|
|
continue;
|
|
|
|
closure = g_cclosure_new_swap (method->impl, target, NULL);
|
|
g_closure_set_meta_marshal (closure, NULL, nm_exported_object_meta_marshal);
|
|
method_signal = g_signal_connect_closure (interface, method->method_name, closure, FALSE);
|
|
|
|
if (method_signal != 0)
|
|
skeleton_data->method_signals[j++] = method_signal;
|
|
}
|
|
skeleton_data->method_signals[j++] = 0;
|
|
|
|
g_object_set_qdata ((GObject *) interface, _skeleton_data_quark (), skeleton_data);
|
|
|
|
return interface;
|
|
}
|
|
|
|
static void
|
|
nm_exported_object_create_skeletons (NMExportedObject *self,
|
|
GType object_type)
|
|
{
|
|
NMExportedObjectPrivate *priv;
|
|
GObjectClass *object_class;
|
|
NMExportedObjectClassInfo *classinfo;
|
|
GSList *iter;
|
|
const NMExportedObjectDBusMethodImpl *methods;
|
|
guint i, methods_len;
|
|
guint num_interfaces;
|
|
InterfaceData *interfaces;
|
|
|
|
classinfo = g_type_get_qdata (object_type, nm_exported_object_class_info_quark ());
|
|
if (!classinfo)
|
|
return;
|
|
|
|
object_class = g_type_class_peek (object_type);
|
|
priv = NM_EXPORTED_OBJECT_GET_PRIVATE (self);
|
|
|
|
methods = classinfo->methods->len ? &g_array_index (classinfo->methods, NMExportedObjectDBusMethodImpl, 0) : NULL;
|
|
methods_len = classinfo->methods->len;
|
|
|
|
num_interfaces = g_slist_length (classinfo->skeleton_types);
|
|
g_return_if_fail (num_interfaces > 0);
|
|
|
|
interfaces = g_slice_alloc (sizeof (InterfaceData) * (num_interfaces + priv->num_interfaces));
|
|
|
|
for (i = num_interfaces, iter = classinfo->skeleton_types; iter; iter = iter->next) {
|
|
InterfaceData *ifdata = &interfaces[--i];
|
|
|
|
ifdata->interface = nm_exported_object_skeleton_create (GPOINTER_TO_SIZE (iter->data),
|
|
object_class,
|
|
methods,
|
|
methods_len,
|
|
(GObject *) self);
|
|
g_dbus_object_skeleton_add_interface ((GDBusObjectSkeleton *) self, ifdata->interface);
|
|
|
|
ifdata->property_changed_signal_id = g_signal_lookup ("properties-changed", G_OBJECT_TYPE (ifdata->interface));
|
|
|
|
ifdata->pending_notifies = g_hash_table_new_full (nm_direct_hash,
|
|
NULL,
|
|
NULL,
|
|
(GDestroyNotify) g_variant_unref);
|
|
}
|
|
nm_assert (i == 0);
|
|
|
|
/* The list of interfaces priv->interfaces is to be sorted from parent-class to derived-class.
|
|
* On the other hand, if one class defines multiple interfaces, the interfaces are sorted in
|
|
* the order of calls to nm_exported_object_class_add_interface(). */
|
|
if (priv->num_interfaces > 0) {
|
|
memcpy (&interfaces[num_interfaces], priv->interfaces, sizeof (InterfaceData) * priv->num_interfaces);
|
|
g_slice_free1 (sizeof (InterfaceData) * priv->num_interfaces, priv->interfaces);
|
|
}
|
|
|
|
priv->num_interfaces = num_interfaces + priv->num_interfaces;
|
|
priv->interfaces = interfaces;
|
|
}
|
|
|
|
void
|
|
nm_exported_object_skeleton_release (GDBusInterfaceSkeleton *interface)
|
|
{
|
|
SkeletonData *skeleton_data;
|
|
guint j;
|
|
|
|
g_return_if_fail (G_IS_DBUS_INTERFACE_SKELETON (interface));
|
|
|
|
skeleton_data = g_object_steal_qdata ((GObject *) interface, _skeleton_data_quark ());
|
|
|
|
for (j = 0; skeleton_data->prop_bindings[j]; j++)
|
|
g_object_unref (skeleton_data->prop_bindings[j]);
|
|
for (j = 0; skeleton_data->method_signals[j]; j++)
|
|
g_signal_handler_disconnect (interface, skeleton_data->method_signals[j]);
|
|
|
|
g_free (skeleton_data->prop_bindings);
|
|
g_free (skeleton_data->method_signals);
|
|
g_slice_free (SkeletonData, skeleton_data);
|
|
|
|
g_object_unref (interface);
|
|
}
|
|
|
|
static void
|
|
nm_exported_object_destroy_skeletons (NMExportedObject *self)
|
|
{
|
|
NMExportedObjectPrivate *priv = NM_EXPORTED_OBJECT_GET_PRIVATE (self);
|
|
guint n;
|
|
|
|
g_return_if_fail (priv->num_interfaces > 0);
|
|
nm_assert (priv->interfaces);
|
|
|
|
n = priv->num_interfaces;
|
|
|
|
while (priv->num_interfaces > 0) {
|
|
InterfaceData *ifdata = &priv->interfaces[--priv->num_interfaces];
|
|
|
|
g_dbus_object_skeleton_remove_interface ((GDBusObjectSkeleton *) self, ifdata->interface);
|
|
nm_exported_object_skeleton_release (ifdata->interface);
|
|
g_hash_table_destroy (ifdata->pending_notifies);
|
|
}
|
|
|
|
g_slice_free1 (sizeof (InterfaceData) * n, priv->interfaces);
|
|
priv->interfaces = NULL;
|
|
}
|
|
|
|
static char *
|
|
_create_export_path (NMExportedObjectClass *klass)
|
|
{
|
|
const char *class_export_path, *p;
|
|
static GHashTable *prefix_counters;
|
|
guint64 *counter;
|
|
|
|
class_export_path = klass->export_path;
|
|
|
|
nm_assert (class_export_path);
|
|
|
|
p = strchr (class_export_path, '%');
|
|
if (p) {
|
|
if (G_UNLIKELY (!prefix_counters))
|
|
prefix_counters = g_hash_table_new (nm_str_hash, g_str_equal);
|
|
|
|
nm_assert (p[1] == 'l');
|
|
nm_assert (p[2] == 'l');
|
|
nm_assert (p[3] == 'u');
|
|
nm_assert (p[4] == '\0');
|
|
|
|
counter = g_hash_table_lookup (prefix_counters, class_export_path);
|
|
if (!counter) {
|
|
counter = g_slice_new0 (guint64);
|
|
g_hash_table_insert (prefix_counters, (char *) class_export_path, counter);
|
|
}
|
|
|
|
NM_PRAGMA_WARNING_DISABLE("-Wformat-nonliteral")
|
|
return g_strdup_printf (class_export_path, (unsigned long long) (++(*counter)));
|
|
NM_PRAGMA_WARNING_REENABLE
|
|
}
|
|
|
|
return g_strdup (class_export_path);
|
|
}
|
|
|
|
/**
|
|
* nm_exported_object_get_path:
|
|
* @self: an #NMExportedObject
|
|
*
|
|
* Gets @self's D-Bus path.
|
|
*
|
|
* Returns: @self's D-Bus path, or %NULL if @self is not exported.
|
|
*/
|
|
const char *
|
|
nm_exported_object_get_path (NMExportedObject *self)
|
|
{
|
|
g_return_val_if_fail (NM_IS_EXPORTED_OBJECT (self), NULL);
|
|
|
|
return NM_EXPORTED_OBJECT_GET_PRIVATE (self)->path;
|
|
}
|
|
|
|
/**
|
|
* nm_exported_object_is_exported:
|
|
* @self: an #NMExportedObject
|
|
*
|
|
* Checks if @self is exported
|
|
*
|
|
* Returns: %TRUE if @self is exported
|
|
*/
|
|
gboolean
|
|
nm_exported_object_is_exported (NMExportedObject *self)
|
|
{
|
|
g_return_val_if_fail (NM_IS_EXPORTED_OBJECT (self), FALSE);
|
|
|
|
return NM_EXPORTED_OBJECT_GET_PRIVATE (self)->path != NULL;
|
|
}
|
|
|
|
/**
|
|
* nm_exported_object_export:
|
|
* @self: an #NMExportedObject
|
|
*
|
|
* Exports @self on all active and future D-Bus connections.
|
|
*
|
|
* The path to export @self on is taken from its #NMObjectClass's %export_path
|
|
* member. If the %export_path contains "%u", then it will be replaced with a
|
|
* monotonically increasing integer ID (with each distinct %export_path having
|
|
* its own counter). Otherwise, %export_path will be used literally (implying
|
|
* that @self must be a singleton).
|
|
*
|
|
* Returns: the path @self was exported under
|
|
*/
|
|
const char *
|
|
nm_exported_object_export (NMExportedObject *self)
|
|
{
|
|
NMExportedObjectPrivate *priv;
|
|
GType type;
|
|
|
|
g_return_val_if_fail (NM_IS_EXPORTED_OBJECT (self), NULL);
|
|
priv = NM_EXPORTED_OBJECT_GET_PRIVATE (self);
|
|
|
|
g_return_val_if_fail (!priv->path, priv->path);
|
|
g_return_val_if_fail (!priv->bus_mgr, priv->path);
|
|
|
|
#ifdef _ASSERT_NO_EARLY_EXPORT
|
|
nm_assert (priv->_constructed);
|
|
#endif
|
|
|
|
priv->bus_mgr = nm_bus_manager_get ();
|
|
if (!priv->bus_mgr)
|
|
g_return_val_if_reached (NULL);
|
|
g_object_add_weak_pointer ((GObject *) priv->bus_mgr, (gpointer *) &priv->bus_mgr);
|
|
|
|
type = G_OBJECT_TYPE (self);
|
|
while (type != NM_TYPE_EXPORTED_OBJECT) {
|
|
nm_exported_object_create_skeletons (self, type);
|
|
type = g_type_parent (type);
|
|
}
|
|
|
|
priv->path = _create_export_path (NM_EXPORTED_OBJECT_GET_CLASS (self));
|
|
|
|
_LOGT ("export: \"%s\"", priv->path);
|
|
g_dbus_object_skeleton_set_object_path (G_DBUS_OBJECT_SKELETON (self), priv->path);
|
|
|
|
/* Important: priv->path and priv->interfaces must not change while
|
|
* the object is registered. */
|
|
|
|
nm_bus_manager_register_object (priv->bus_mgr, (GDBusObjectSkeleton *) self);
|
|
|
|
_notify (self, PROP_PATH);
|
|
|
|
return priv->path;
|
|
}
|
|
|
|
/**
|
|
* nm_exported_object_unexport:
|
|
* @self: an #NMExportedObject
|
|
*
|
|
* Unexports @self on all active D-Bus connections (and prevents it from being
|
|
* auto-exported on future connections).
|
|
*/
|
|
void
|
|
nm_exported_object_unexport (NMExportedObject *self)
|
|
{
|
|
NMExportedObjectPrivate *priv;
|
|
|
|
g_return_if_fail (NM_IS_EXPORTED_OBJECT (self));
|
|
priv = NM_EXPORTED_OBJECT_GET_PRIVATE (self);
|
|
|
|
g_return_if_fail (priv->path);
|
|
|
|
/* Important: priv->path and priv->interfaces must not change while
|
|
* the object is registered. */
|
|
|
|
_LOGT ("unexport: \"%s\"", priv->path);
|
|
|
|
if (priv->bus_mgr) {
|
|
nm_bus_manager_unregister_object (priv->bus_mgr, (GDBusObjectSkeleton *) self);
|
|
g_object_remove_weak_pointer ((GObject *) priv->bus_mgr, (gpointer *) &priv->bus_mgr);
|
|
priv->bus_mgr = NULL;
|
|
}
|
|
|
|
nm_exported_object_destroy_skeletons (self);
|
|
|
|
g_dbus_object_skeleton_set_object_path ((GDBusObjectSkeleton *) self, NULL);
|
|
|
|
g_clear_pointer (&priv->path, g_free);
|
|
|
|
nm_clear_g_source (&priv->notify_idle_id);
|
|
|
|
_notify (self, PROP_PATH);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
void
|
|
_nm_exported_object_clear_and_unexport (NMExportedObject **location)
|
|
{
|
|
NMExportedObject *self;
|
|
NMExportedObjectPrivate *priv;
|
|
|
|
if (!location || !*location)
|
|
return;
|
|
|
|
self = *location;
|
|
*location = NULL;
|
|
|
|
g_return_if_fail (NM_IS_EXPORTED_OBJECT (self));
|
|
|
|
priv = NM_EXPORTED_OBJECT_GET_PRIVATE (self);
|
|
|
|
if (priv->path)
|
|
nm_exported_object_unexport (self);
|
|
|
|
g_object_unref (self);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
GDBusInterfaceSkeleton *
|
|
nm_exported_object_get_interface_by_type (NMExportedObject *self, GType interface_type)
|
|
{
|
|
NMExportedObjectPrivate *priv;
|
|
guint i;
|
|
|
|
g_return_val_if_fail (NM_IS_EXPORTED_OBJECT (self), NULL);
|
|
|
|
priv = NM_EXPORTED_OBJECT_GET_PRIVATE (self);
|
|
|
|
g_return_val_if_fail (priv->path, NULL);
|
|
g_return_val_if_fail (priv->num_interfaces > 0, NULL);
|
|
|
|
nm_assert (priv->interfaces);
|
|
|
|
for (i = 0; i < priv->num_interfaces; i++) {
|
|
InterfaceData *ifdata = &priv->interfaces[i];
|
|
|
|
if (G_TYPE_CHECK_INSTANCE_TYPE (ifdata->interface, interface_type))
|
|
return ifdata->interface;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
void
|
|
nm_exported_object_class_set_quitting (void)
|
|
{
|
|
quitting = TRUE;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
typedef struct {
|
|
const char *property_name;
|
|
GVariant *variant;
|
|
} PendingNotifiesItem;
|
|
|
|
static int
|
|
_sort_pending_notifies (gconstpointer a, gconstpointer b, gpointer user_data)
|
|
{
|
|
return strcmp (((const PendingNotifiesItem *) a)->property_name,
|
|
((const PendingNotifiesItem *) b)->property_name);
|
|
}
|
|
|
|
static gboolean
|
|
idle_emit_properties_changed (gpointer self)
|
|
{
|
|
NMExportedObjectPrivate *priv = NM_EXPORTED_OBJECT_GET_PRIVATE (NM_EXPORTED_OBJECT (self));
|
|
guint k;
|
|
|
|
priv->notify_idle_id = 0;
|
|
|
|
for (k = 0; k < priv->num_interfaces; k++) {
|
|
InterfaceData *ifdata = &priv->interfaces[k];
|
|
gs_unref_variant GVariant *variant = NULL;
|
|
PendingNotifiesItem *values;
|
|
GVariantBuilder notifies;
|
|
GHashTableIter hash_iter;
|
|
guint i, n;
|
|
|
|
n = g_hash_table_size (ifdata->pending_notifies);
|
|
if (n == 0)
|
|
continue;
|
|
|
|
nm_assert (ifdata->property_changed_signal_id);
|
|
|
|
/* We use here alloca in a loop, something that is usually avoided.
|
|
* But the number of interfaces "priv->num_interfaces" is small (determined by
|
|
* the depth of the type inheritance) and the number of possible pending_notifies
|
|
* "n" is small (determined by the number of GObject properties). */
|
|
values = g_alloca (sizeof (values[0]) * n);
|
|
|
|
i = 0;
|
|
g_hash_table_iter_init (&hash_iter, ifdata->pending_notifies);
|
|
while (g_hash_table_iter_next (&hash_iter, (gpointer) &values[i].property_name, (gpointer) &values[i].variant))
|
|
i++;
|
|
nm_assert (i == n);
|
|
|
|
g_qsort_with_data (values, n, sizeof (values[0]), _sort_pending_notifies, NULL);
|
|
|
|
g_variant_builder_init (¬ifies, G_VARIANT_TYPE_VARDICT);
|
|
for (i = 0; i < n; i++)
|
|
g_variant_builder_add (¬ifies, "{sv}", values[i].property_name, values[i].variant);
|
|
variant = g_variant_ref_sink (g_variant_builder_end (¬ifies));
|
|
|
|
|
|
if (_LOG2D_ENABLED ()) {
|
|
gs_free char *notification = g_variant_print (variant, TRUE);
|
|
|
|
_LOG2D ("type %s, iface %s: %s",
|
|
G_OBJECT_TYPE_NAME (self), G_OBJECT_TYPE_NAME (ifdata->interface),
|
|
notification);
|
|
}
|
|
|
|
g_signal_emit (ifdata->interface, ifdata->property_changed_signal_id, 0, variant);
|
|
|
|
g_hash_table_remove_all (ifdata->pending_notifies);
|
|
}
|
|
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
|
|
static void
|
|
nm_exported_object_notify (GObject *object, GParamSpec *pspec)
|
|
{
|
|
NMExportedObject *self = (NMExportedObject *) object;
|
|
NMExportedObjectPrivate *priv = NM_EXPORTED_OBJECT_GET_PRIVATE (self);
|
|
NMExportedObjectClassInfo *classinfo;
|
|
GType type;
|
|
const char *dbus_property_name = NULL;
|
|
GValue value = G_VALUE_INIT;
|
|
GVariant *value_variant;
|
|
InterfaceData *ifdata = NULL;
|
|
const GVariantType *vtype;
|
|
guint i, j;
|
|
|
|
/* Hook to emit deprecated "PropertiesChanged" signal on NetworkManager interfaces.
|
|
* This is to preserve deprecated D-Bus API, nowadays we use instead
|
|
* the "PropertiesChanged" signal of "org.freedesktop.DBus.Properties". */
|
|
|
|
if (priv->num_interfaces == 0)
|
|
return;
|
|
|
|
for (type = G_OBJECT_TYPE (self); type; type = g_type_parent (type)) {
|
|
classinfo = g_type_get_qdata (type, nm_exported_object_class_info_quark ());
|
|
if (!classinfo)
|
|
continue;
|
|
|
|
dbus_property_name = g_hash_table_lookup (classinfo->properties, pspec->name);
|
|
if (dbus_property_name)
|
|
break;
|
|
}
|
|
if (!dbus_property_name) {
|
|
_LOG2T ("ignoring notification for prop %s on type %s",
|
|
pspec->name, G_OBJECT_TYPE_NAME (self));
|
|
return;
|
|
}
|
|
|
|
for (i = 0; i < priv->num_interfaces; i++) {
|
|
GDBusInterfaceInfo *iinfo;
|
|
|
|
ifdata = &priv->interfaces[i];
|
|
iinfo = g_dbus_interface_skeleton_get_info (ifdata->interface);
|
|
for (j = 0; iinfo->properties[j]; j++) {
|
|
if (nm_streq (iinfo->properties[j]->name, dbus_property_name)) {
|
|
vtype = G_VARIANT_TYPE (iinfo->properties[j]->signature);
|
|
goto vtype_found;
|
|
}
|
|
}
|
|
}
|
|
g_return_if_reached ();
|
|
|
|
vtype_found:
|
|
g_value_init (&value, pspec->value_type);
|
|
g_object_get_property ((GObject *) self, pspec->name, &value);
|
|
value_variant = g_dbus_gvalue_to_gvariant (&value, vtype);
|
|
g_value_unset (&value);
|
|
|
|
if ( ( NM_IS_DEVICE (self)
|
|
&& !NMDBUS_IS_DEVICE_STATISTICS_SKELETON (ifdata->interface))
|
|
|| NM_IS_ACTIVE_CONNECTION (self)) {
|
|
/* This PropertiesChanged signal is nodaways deprecated in favor
|
|
* of "org.freedesktop.DBus.Properties"'s PropertiesChanged signal.
|
|
* This function solely exists to raise the NM version of PropertiesChanged.
|
|
*
|
|
* With types exported on D-Bus that are implemented as derived
|
|
* types in glib (NMDevice and NMActiveConnection), multiple types
|
|
* in the inheritance tree define a "PropertiesChanged" signal.
|
|
*
|
|
* In 1.0.0 and earlier, the signal was emitted once for every interface
|
|
* that had a "PropertiesChanged" signal. For example:
|
|
* - NMDeviceEthernet.HwAddress was emitted on "fdo.NM.Device.Ethernet"
|
|
* and "fdo.NM.Device.Veth" (if the device was of type NMDeviceVeth).
|
|
* - NMVpnConnection.VpnState was emitted on "fdo.NM.Connecion.Active"
|
|
* and "fdo.NM.VPN.Connection".
|
|
*
|
|
* NMDevice is special in that it didn't have a "PropertiesChanged" signal.
|
|
* Thus, a change to "NMDevice.StateReason" would be emitted on "fdo.NM.Device.Ethernet"
|
|
* and also on "fdo.NM.Device.Veth" (in case of a device of type NMDeviceVeth).
|
|
*
|
|
* The releases of 1.2.0 and 1.4.0 failed to realize above and broke this behavior.
|
|
* This special handling here is to bring back the 1.0.0 behavior.
|
|
*
|
|
* The Device.Statistics signal is special, because it was only added with 1.4.0
|
|
* and didn't have above behavior. So let's save the overhead of emitting multiple
|
|
* deprecated signals for wrong interfaces. */
|
|
for (i = 0, j = 0; i < priv->num_interfaces; i++) {
|
|
ifdata = &priv->interfaces[i];
|
|
if ( ifdata->property_changed_signal_id
|
|
&& !NMDBUS_IS_DEVICE_STATISTICS_SKELETON (ifdata->interface)) {
|
|
j++;
|
|
g_hash_table_insert (ifdata->pending_notifies,
|
|
(gpointer) dbus_property_name,
|
|
g_variant_ref (value_variant));
|
|
}
|
|
}
|
|
nm_assert (j > 0);
|
|
g_variant_unref (value_variant);
|
|
} else if (ifdata->property_changed_signal_id) {
|
|
/* @dbus_property_name is inside classinfo and never freed, thus we don't clone it.
|
|
* Also, we do a pointer, not string comparison. */
|
|
g_hash_table_insert (ifdata->pending_notifies,
|
|
(gpointer) dbus_property_name,
|
|
value_variant);
|
|
} else
|
|
g_variant_unref (value_variant);
|
|
|
|
if (!priv->notify_idle_id)
|
|
priv->notify_idle_id = g_idle_add (idle_emit_properties_changed, self);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
static void
|
|
get_property (GObject *object, guint prop_id,
|
|
GValue *value, GParamSpec *pspec)
|
|
{
|
|
NMExportedObject *self = NM_EXPORTED_OBJECT (object);
|
|
NMExportedObjectPrivate *priv = NM_EXPORTED_OBJECT_GET_PRIVATE (self);
|
|
|
|
switch (prop_id) {
|
|
case PROP_PATH:
|
|
g_value_set_string (value, priv->path);
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
nm_exported_object_init (NMExportedObject *self)
|
|
{
|
|
NMExportedObjectPrivate *priv;
|
|
|
|
priv = G_TYPE_INSTANCE_GET_PRIVATE (self, NM_TYPE_EXPORTED_OBJECT, NMExportedObjectPrivate);
|
|
self->_priv = priv;
|
|
}
|
|
|
|
static void
|
|
constructed (GObject *object)
|
|
{
|
|
NMExportedObjectClass *klass;
|
|
|
|
G_OBJECT_CLASS (nm_exported_object_parent_class)->constructed (object);
|
|
|
|
#ifdef _ASSERT_NO_EARLY_EXPORT
|
|
NM_EXPORTED_OBJECT_GET_PRIVATE (NM_EXPORTED_OBJECT (object))->_constructed = TRUE;
|
|
#endif
|
|
|
|
klass = NM_EXPORTED_OBJECT_GET_CLASS (object);
|
|
|
|
if (klass->export_on_construction)
|
|
nm_exported_object_export ((NMExportedObject *) object);
|
|
}
|
|
|
|
static void
|
|
dispose (GObject *object)
|
|
{
|
|
NMExportedObject *self = NM_EXPORTED_OBJECT (object);
|
|
NMExportedObjectPrivate *priv = NM_EXPORTED_OBJECT_GET_PRIVATE (self);
|
|
|
|
/* Objects should have already been unexported by their owner, unless
|
|
* we are quitting, where many objects stick around until exit.
|
|
*/
|
|
if (!quitting) {
|
|
if (priv->path) {
|
|
g_warn_if_reached ();
|
|
nm_exported_object_unexport (self);
|
|
}
|
|
} else if (nm_clear_g_free (&priv->path))
|
|
_notify (self, PROP_PATH);
|
|
|
|
nm_clear_g_source (&priv->notify_idle_id);
|
|
|
|
G_OBJECT_CLASS (nm_exported_object_parent_class)->dispose (object);
|
|
}
|
|
|
|
static void
|
|
nm_exported_object_class_init (NMExportedObjectClass *klass)
|
|
{
|
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
|
|
|
g_type_class_add_private (object_class, sizeof (NMExportedObjectPrivate));
|
|
|
|
object_class->constructed = constructed;
|
|
object_class->notify = nm_exported_object_notify;
|
|
object_class->dispose = dispose;
|
|
object_class->get_property = get_property;
|
|
|
|
obj_properties[PROP_PATH] =
|
|
g_param_spec_string (NM_EXPORTED_OBJECT_PATH, "", "",
|
|
NULL,
|
|
G_PARAM_READABLE |
|
|
G_PARAM_STATIC_STRINGS);
|
|
|
|
g_object_class_install_properties (object_class, _PROPERTY_ENUMS_LAST, obj_properties);
|
|
}
|