NetworkManager/libnm/nm-object.c
Thomas Haller aed6e28461 trivial: avoid XXX tag and replace by NOTE or FIXME
XXX was used to either raise attention (NOTE) or to indicate
that this is ugly code that should be fixed (FIXME). The usage
was inconsistent.

Let's avoid XXX and use either NOTE or FIXME.
2018-01-23 12:55:33 +01:00

1342 lines
36 KiB
C

/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
/*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301 USA.
*
* Copyright 2007 - 2008 Novell, Inc.
* Copyright 2007 - 2012 Red Hat, Inc.
*/
#include "nm-default.h"
#include "nm-object.h"
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include "nm-utils.h"
#include "nm-dbus-interface.h"
#include "nm-object-private.h"
#include "nm-dbus-helpers.h"
#include "nm-client.h"
#include "nm-core-internal.h"
#include "nm-utils/c-list.h"
static gboolean debug = FALSE;
#define dbgmsg(f,...) if (G_UNLIKELY (debug)) { g_message (f, ## __VA_ARGS__ ); }
NM_CACHED_QUARK_FCN ("nm-obj-nm", _nm_object_obj_nm_quark)
static void nm_object_initable_iface_init (GInitableIface *iface);
static void nm_object_async_initable_iface_init (GAsyncInitableIface *iface);
typedef struct {
GSList *interfaces;
} NMObjectClassPrivate;
#define NM_OBJECT_CLASS_GET_PRIVATE(k) (G_TYPE_CLASS_GET_PRIVATE ((k), NM_TYPE_OBJECT, NMObjectClassPrivate))
G_DEFINE_ABSTRACT_TYPE_WITH_CODE (NMObject, nm_object, G_TYPE_OBJECT,
g_type_add_class_private (g_define_type_id, sizeof (NMObjectClassPrivate));
G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, nm_object_initable_iface_init);
G_IMPLEMENT_INTERFACE (G_TYPE_ASYNC_INITABLE, nm_object_async_initable_iface_init);
)
#define NM_OBJECT_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NM_TYPE_OBJECT, NMObjectPrivate))
typedef struct {
PropertyMarshalFunc func;
GType object_type;
gpointer field;
const char *signal_prefix;
} PropertyInfo;
static void reload_complete (NMObject *object, gboolean emit_now);
static gboolean demarshal_generic (NMObject *object, GParamSpec *pspec, GVariant *value, gpointer field);
typedef struct {
GDBusObject *object;
GDBusObjectManager *object_manager;
GSList *property_tables;
NMObject *parent;
gboolean inited; /* async init finished? */
GSList *waiters; /* if async init did not finish, users of this object need
* to defer their notifications by adding themselves here. */
CList notify_items;
guint notify_id;
guint reload_remaining;
CList pending; /* ordered list of pending property updates. */
GPtrArray *proxies;
} NMObjectPrivate;
enum {
PROP_0,
PROP_PATH,
PROP_DBUS_CONNECTION,
PROP_NM_RUNNING,
PROP_DBUS_OBJECT,
PROP_DBUS_OBJECT_MANAGER,
LAST_PROP
};
/**
* nm_object_get_path:
* @object: a #NMObject
*
* Gets the DBus path of the #NMObject.
*
* Returns: the object's path. This is the internal string used by the
* object, and must not be modified.
**/
const char *
nm_object_get_path (NMObject *object)
{
g_return_val_if_fail (NM_IS_OBJECT (object), NULL);
return g_dbus_object_get_object_path (NM_OBJECT_GET_PRIVATE (object)->object);
}
/**
* _nm_object_get_proxy:
* @object: an #NMObject
* @interface: a D-Bus interface implemented by @object
*
* Gets the D-Bus proxy for @interface on @object.
*
* Returns: (transfer none): a D-Bus proxy
*/
GDBusProxy *
_nm_object_get_proxy (NMObject *object,
const char *interface)
{
NMObjectPrivate *priv;
GDBusInterface *proxy;
g_return_val_if_fail (NM_IS_OBJECT (object), NULL);
priv = NM_OBJECT_GET_PRIVATE (object);
if (priv->object == NULL)
return NULL;
proxy = g_dbus_object_get_interface (priv->object, interface);
g_return_val_if_fail (proxy != NULL, NULL);
return G_DBUS_PROXY (proxy);
}
typedef enum {
NOTIFY_SIGNAL_PENDING_NONE,
NOTIFY_SIGNAL_PENDING_ADDED,
NOTIFY_SIGNAL_PENDING_REMOVED,
NOTIFY_SIGNAL_PENDING_ADDED_REMOVED,
} NotifySignalPending;
typedef struct {
CList lst;
const char *property;
const char *signal_prefix;
NotifySignalPending pending;
NMObject *changed;
} NotifyItem;
static void
notify_item_free (NotifyItem *item)
{
c_list_unlink_stale (&item->lst);
g_clear_object (&item->changed);
g_slice_free (NotifyItem, item);
}
static gboolean
deferred_notify_cb (gpointer data)
{
NMObject *object = NM_OBJECT (data);
NMObjectPrivate *priv = NM_OBJECT_GET_PRIVATE (object);
NMObjectClass *object_class = NM_OBJECT_GET_CLASS (object);
CList props;
CList *iter, *safe;
priv->notify_id = 0;
/* Wait until all reloads are done before notifying */
if (priv->reload_remaining)
return G_SOURCE_REMOVE;
/* Clear priv->notify_items early so that an NMObject subclass that
* listens to property changes can queue up other property changes
* during the g_object_notify() call separately from the property
* list we're iterating.
*/
c_list_link_after (&priv->notify_items, &props);
c_list_unlink (&priv->notify_items);
g_object_ref (object);
/* Emit added/removed signals first since some of our internal objects
* use the added/removed signals for new object processing.
*/
c_list_for_each (iter, &props) {
NotifyItem *item = c_list_entry (iter, NotifyItem, lst);
char buf[50];
gint ret = 0;
switch (item->pending) {
case NOTIFY_SIGNAL_PENDING_ADDED:
ret = g_snprintf (buf, sizeof (buf), "%s-added", item->signal_prefix);
break;
case NOTIFY_SIGNAL_PENDING_REMOVED:
ret = g_snprintf (buf, sizeof (buf), "%s-removed", item->signal_prefix);
break;
case NOTIFY_SIGNAL_PENDING_ADDED_REMOVED:
if (object_class->object_creation_failed)
object_class->object_creation_failed (object, nm_object_get_path (item->changed));
break;
case NOTIFY_SIGNAL_PENDING_NONE:
default:
break;
}
if (ret > 0) {
g_assert (ret < sizeof (buf));
g_signal_emit_by_name (object, buf, item->changed);
}
}
/* Emit property change notifications second */
c_list_for_each (iter, &props) {
NotifyItem *item = c_list_entry (iter, NotifyItem, lst);
if (item->property)
g_object_notify (G_OBJECT (object), item->property);
}
g_object_unref (object);
c_list_for_each_safe (iter, safe, &props)
notify_item_free (c_list_entry (iter, NotifyItem, lst));
return G_SOURCE_REMOVE;
}
static void
_nm_object_defer_notify (NMObject *object)
{
NMObjectPrivate *priv = NM_OBJECT_GET_PRIVATE (object);
if (!priv->notify_id)
priv->notify_id = g_idle_add_full (G_PRIORITY_LOW, deferred_notify_cb, object, NULL);
}
static void
_nm_object_queue_notify_full (NMObject *object,
const char *property,
const char *signal_prefix,
gboolean added,
NMObject *changed)
{
NMObjectPrivate *priv;
NotifyItem *item;
CList *iter;
g_return_if_fail (NM_IS_OBJECT (object));
g_return_if_fail (!signal_prefix != !property);
g_return_if_fail (!signal_prefix == !changed);
priv = NM_OBJECT_GET_PRIVATE (object);
_nm_object_defer_notify (object);
property = g_intern_string (property);
signal_prefix = g_intern_string (signal_prefix);
c_list_for_each (iter, &priv->notify_items) {
item = c_list_entry (iter, NotifyItem, lst);
if (property && (property == item->property))
return;
/* Collapse signals for the same object (such as "added->removed") to
* ensure we don't emit signals when their sum should have no effect.
* The "added->removed->removed" sequence requires special handling,
* hence the addition of the ADDED_REMOVED state to ensure that no
* signal is emitted in this case:
*
* Without the ADDED_REMOVED state:
* NONE + added -> ADDED
* ADDED + removed -> NONE
* NONE + removed -> REMOVED (would emit 'removed' signal)
*
* With the ADDED_REMOVED state:
* NONE | ADDED_REMOVED + added -> ADDED
* ADDED + removed -> ADDED_REMOVED
* ADDED_REMOVED + removed -> ADDED_REMOVED (emits no signal)
*/
if (signal_prefix && (changed == item->changed) && (item->signal_prefix == signal_prefix)) {
switch (item->pending) {
case NOTIFY_SIGNAL_PENDING_ADDED:
if (!added)
item->pending = NOTIFY_SIGNAL_PENDING_ADDED_REMOVED;
break;
case NOTIFY_SIGNAL_PENDING_REMOVED:
if (added)
item->pending = NOTIFY_SIGNAL_PENDING_NONE;
break;
case NOTIFY_SIGNAL_PENDING_ADDED_REMOVED:
if (added)
item->pending = NOTIFY_SIGNAL_PENDING_ADDED;
break;
case NOTIFY_SIGNAL_PENDING_NONE:
item->pending = added ? NOTIFY_SIGNAL_PENDING_ADDED : NOTIFY_SIGNAL_PENDING_REMOVED;
break;
default:
g_assert_not_reached ();
}
return;
}
}
item = g_slice_new0 (NotifyItem);
item->property = property;
if (signal_prefix) {
item->signal_prefix = signal_prefix;
item->pending = added ? NOTIFY_SIGNAL_PENDING_ADDED : NOTIFY_SIGNAL_PENDING_REMOVED;
item->changed = changed ? g_object_ref (changed) : NULL;
}
c_list_link_tail (&priv->notify_items, &item->lst);
}
void
_nm_object_queue_notify (NMObject *object, const char *property)
{
_nm_object_queue_notify_full (object, property, NULL, FALSE, NULL);
}
typedef struct {
CList lst_pending;
NMObject *self;
PropertyInfo *pi;
GObject **objects;
int length, remaining;
gboolean array;
const char *property_name;
} ObjectCreatedData;
static void
odata_free (gpointer data)
{
ObjectCreatedData *odata = data;
c_list_unlink_stale (&odata->lst_pending);
g_object_unref (odata->self);
g_free (odata->objects);
g_slice_free (ObjectCreatedData, odata);
}
static void object_property_maybe_complete (NMObject *self);
/* Stolen from dbus-glib */
static char*
wincaps_to_dash (const char *caps)
{
const char *p;
GString *str;
str = g_string_new (NULL);
p = caps;
while (*p) {
if (g_ascii_isupper (*p)) {
if (str->len > 0 && (str->len < 2 || str->str[str->len-2] != '-'))
g_string_append_c (str, '-');
g_string_append_c (str, g_ascii_tolower (*p));
} else
g_string_append_c (str, *p);
++p;
}
return g_string_free (str, FALSE);
}
/* Adds object to array if it's not already there */
static void
add_to_object_array_unique (GPtrArray *array, GObject *obj)
{
guint i;
g_return_if_fail (array != NULL);
if (obj != NULL) {
for (i = 0; i < array->len; i++) {
if (g_ptr_array_index (array, i) == obj) {
g_object_unref (obj);
return;
}
}
g_ptr_array_add (array, obj);
}
}
/* Places items from 'needles' that are not in 'haystack' into 'diff' */
static void
array_diff (GPtrArray *needles, GPtrArray *haystack, GPtrArray *diff)
{
guint i, j;
GObject *obj;
g_assert (needles);
g_assert (haystack);
g_assert (diff);
for (i = 0; i < needles->len; i++) {
obj = g_ptr_array_index (needles, i);
for (j = 0; j < haystack->len; j++) {
if (g_ptr_array_index (haystack, j) == obj)
break;
}
if (j == haystack->len)
g_ptr_array_add (diff, obj);
}
}
static void
queue_added_removed_signal (NMObject *self,
const char *signal_prefix,
NMObject *changed,
gboolean added)
{
_nm_object_queue_notify_full (self, NULL, signal_prefix, added, changed);
}
static gboolean
already_awaits (ObjectCreatedData *odata, GObject *object)
{
NMObject *self = odata->self;
NMObjectPrivate *priv = NM_OBJECT_GET_PRIVATE (self);
GSList *iter;
if ((GObject *)odata->self == object)
return TRUE;
for (iter = priv->waiters; iter; iter = g_slist_next (iter)) {
if (already_awaits (iter->data, object))
return TRUE;
}
return FALSE;
}
static void
object_property_maybe_complete (NMObject *self)
{
NMObjectPrivate *priv = NM_OBJECT_GET_PRIVATE (self);
/* The odata may hold the last reference. */
_nm_unused gs_unref_object NMObject *self_keep_alive = g_object_ref (self);
int i;
CList *iter, *safe;
c_list_for_each_safe (iter, safe, &priv->pending) {
ObjectCreatedData *odata = c_list_entry (iter, ObjectCreatedData, lst_pending);
PropertyInfo *pi = odata->pi;
gboolean different = TRUE;
if (odata->remaining > 0)
return;
/* Only complete the array property load when all the objects are initialized. */
for (i = 0; i < odata->length; i++) {
GObject *obj = odata->objects[i];
NMObjectPrivate *obj_priv;
/* Could not load the object. Perhaps it was removed. */
if (!obj)
continue;
obj_priv = NM_OBJECT_GET_PRIVATE (obj);
if (!obj_priv->inited) {
/* The object is not finished because we block its creation. */
if (already_awaits (odata, obj))
continue;
if (!g_slist_find (obj_priv->waiters, odata))
obj_priv->waiters = g_slist_prepend (obj_priv->waiters, odata);
return;
}
}
if (odata->array) {
GPtrArray *old = *((GPtrArray **) pi->field);
GPtrArray *new;
/* Build up new array */
new = g_ptr_array_new_full (odata->length, g_object_unref);
for (i = 0; i < odata->length; i++)
add_to_object_array_unique (new, odata->objects[i]);
*((GPtrArray **) pi->field) = new;
if (pi->signal_prefix) {
GPtrArray *added = g_ptr_array_sized_new (3);
GPtrArray *removed = g_ptr_array_sized_new (3);
if (old) {
/* Find objects in 'old' that do not exist in 'new' */
array_diff (old, new, removed);
/* Find objects in 'new' that do not exist in old */
array_diff (new, old, added);
} else {
for (i = 0; i < new->len; i++)
g_ptr_array_add (added, g_ptr_array_index (new, i));
}
/* Emit added & removed */
for (i = 0; i < removed->len; i++) {
queue_added_removed_signal (self,
pi->signal_prefix,
g_ptr_array_index (removed, i),
FALSE);
}
for (i = 0; i < added->len; i++) {
queue_added_removed_signal (self,
pi->signal_prefix,
g_ptr_array_index (added, i),
TRUE);
}
different = removed->len || added->len;
g_ptr_array_unref (added);
g_ptr_array_unref (removed);
} else {
/* No added/removed signals to send, just replace the property with
* the new values.
*/
different = TRUE;
}
/* Free old array last since it will release references, thus freeing
* any objects in the 'removed' array.
*/
if (old)
g_ptr_array_unref (old);
} else {
GObject **obj_p = pi->field;
different = (*obj_p != odata->objects[0]);
if (*obj_p)
g_object_unref (*obj_p);
*obj_p = odata->objects[0];
}
if (different && odata->property_name)
_nm_object_queue_notify (self, odata->property_name);
if (--priv->reload_remaining == 0)
reload_complete (self, TRUE);
odata_free (odata);
}
}
static void
object_created (GObject *obj, const char *path, gpointer user_data)
{
ObjectCreatedData *odata = user_data;
/* We assume that on error, the creator_func printed something */
if (obj == NULL && g_strcmp0 (path, "/") != 0 ) {
NMObjectClass *object_class = NM_OBJECT_GET_CLASS (odata->self);
if (object_class->object_creation_failed)
object_class->object_creation_failed (odata->self, path);
}
odata->objects[odata->length - odata->remaining--] = obj ? g_object_ref (obj) : NULL;
object_property_maybe_complete (odata->self);
}
static gboolean
handle_object_property (NMObject *self, const char *property_name, GVariant *value,
PropertyInfo *pi)
{
NMObjectPrivate *priv = NM_OBJECT_GET_PRIVATE (self);
gs_unref_object GDBusObject *object = NULL;
GObject *obj;
const char *path;
ObjectCreatedData *odata;
odata = g_slice_new (ObjectCreatedData);
odata->self = g_object_ref (self);
odata->pi = pi;
odata->objects = g_new0 (GObject *, 1);
odata->length = odata->remaining = 1;
odata->array = FALSE;
odata->property_name = property_name;
c_list_link_tail (&priv->pending, &odata->lst_pending);
priv->reload_remaining++;
path = g_variant_get_string (value, NULL);
if (!strcmp (path, "/")) {
object_created (NULL, path, odata);
return TRUE;
}
object = g_dbus_object_manager_get_object (priv->object_manager, path);
if (!object) {
/* This is a server bug -- a dangling object path for an object
* that does not exist.
*
* NOTE: We've ignored this before and the server hits the condition
* more often that it should. Given we're able to recover from
* ther error, let's lower the severity of the log message to
* avoid unnecessarily bothering the user. This can be removed
* once the issue is fixed on the server. */
#if NM_MORE_ASSERTS
#define __nm_log_debug g_warning
#else
#define __nm_log_debug g_debug
#endif
__nm_log_debug ("No object known for %s", path);
#undef __nm_log_debug
return FALSE;
}
obj = g_object_get_qdata (G_OBJECT (object), _nm_object_obj_nm_quark ());
object_created (obj, path, odata);
return TRUE;
}
static gboolean
handle_object_array_property (NMObject *self, const char *property_name, GVariant *value,
PropertyInfo *pi)
{
NMObjectPrivate *priv = NM_OBJECT_GET_PRIVATE (self);
GObject *obj;
GVariantIter iter;
gsize npaths;
const char *path;
ObjectCreatedData *odata;
npaths = g_variant_n_children (value);
odata = g_slice_new (ObjectCreatedData);
odata->self = g_object_ref (self);
odata->pi = pi;
odata->objects = g_new0 (GObject *, npaths);
odata->length = odata->remaining = npaths;
odata->array = TRUE;
odata->property_name = property_name;
c_list_link_tail (&priv->pending, &odata->lst_pending);
priv->reload_remaining++;
if (npaths == 0) {
object_property_maybe_complete (self);
return TRUE;
}
g_variant_iter_init (&iter, value);
while (g_variant_iter_next (&iter, "&o", &path)) {
gs_unref_object GDBusObject *object = NULL;
object = g_dbus_object_manager_get_object (priv->object_manager, path);
if (object) {
obj = g_object_get_qdata (G_OBJECT (object), _nm_object_obj_nm_quark ());
object_created (obj, path, odata);
} else {
g_warning ("no object known for %s\n", path);
odata->remaining--;
odata->length--;
object_property_maybe_complete (self);
}
}
return TRUE;
}
static void
handle_property_changed (NMObject *self, const char *dbus_name, GVariant *value)
{
NMObjectPrivate *priv = NM_OBJECT_GET_PRIVATE (self);
char *prop_name;
PropertyInfo *pi;
GParamSpec *pspec;
gboolean success = FALSE, found = FALSE;
GSList *iter;
prop_name = wincaps_to_dash (dbus_name);
/* Iterate through the object and its parents to find the property */
for (iter = priv->property_tables; iter; iter = g_slist_next (iter)) {
pi = g_hash_table_lookup ((GHashTable *) iter->data, prop_name);
if (pi) {
if (!pi->field) {
/* We know about this property but aren't tracking changes on it. */
goto out;
}
found = TRUE;
break;
}
}
if (!found) {
dbgmsg ("Property '%s' unhandled.", prop_name);
goto out;
}
pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (G_OBJECT (self)), prop_name);
if (!pspec && pi->func == demarshal_generic) {
dbgmsg ("%s: property '%s' changed but wasn't defined by object type %s.",
__func__,
prop_name,
G_OBJECT_TYPE_NAME (self));
goto out;
}
if (G_UNLIKELY (debug)) {
char *s;
s = g_variant_print (value, FALSE);
dbgmsg ("PC: (%p) %s:%s => '%s' (%s%s%s)",
self, G_OBJECT_TYPE_NAME (self),
prop_name,
s,
g_variant_get_type_string (value),
pi->object_type ? " / " : "",
pi->object_type ? g_type_name (pi->object_type) : "");
g_free (s);
}
if (pspec && pi->object_type) {
if (g_variant_is_of_type (value, G_VARIANT_TYPE_OBJECT_PATH))
success = handle_object_property (self, pspec->name, value, pi);
else if (g_variant_is_of_type (value, G_VARIANT_TYPE ("ao")))
success = handle_object_array_property (self, pspec->name, value, pi);
else {
g_warn_if_reached ();
goto out;
}
} else
success = (*(pi->func)) (self, pspec, value, pi->field);
if (!success) {
dbgmsg ("%s: failed to update property '%s' of object type %s.",
__func__,
prop_name,
G_OBJECT_TYPE_NAME (self));
}
out:
g_free (prop_name);
}
static void
properties_changed (GDBusProxy *proxy,
GVariant *changed_properties,
GStrv invalidated_properties,
gpointer user_data)
{
NMObject *self = NM_OBJECT (user_data);
GVariantIter iter;
const char *name;
GVariant *value;
g_variant_iter_init (&iter, changed_properties);
while (g_variant_iter_next (&iter, "{&sv}", &name, &value)) {
handle_property_changed (self, name, value);
g_variant_unref (value);
}
}
#define HANDLE_TYPE(vtype, ctype, getter) \
G_STMT_START { \
if (g_variant_is_of_type (value, vtype)) { \
ctype *param = (ctype *) field; \
ctype newval = getter (value); \
different = *param != newval; \
*param = newval; \
} else { \
success = FALSE; \
goto done; \
} \
} G_STMT_END
static gboolean
demarshal_generic (NMObject *object,
GParamSpec *pspec,
GVariant *value,
gpointer field)
{
gboolean success = TRUE;
gboolean different = FALSE;
if (pspec->value_type == G_TYPE_STRING) {
if (g_variant_is_of_type (value, G_VARIANT_TYPE_STRING)) {
char **param = (char **) field;
const char *newval = g_variant_get_string (value, NULL);
different = !!g_strcmp0 (*param, newval);
if (different) {
g_free (*param);
*param = g_strdup (newval);
}
} else if (g_variant_is_of_type (value, G_VARIANT_TYPE_OBJECT_PATH)) {
char **param = (char **) field;
const char *newval = g_variant_get_string (value, NULL);
/* Handle "NULL" object paths */
if (g_strcmp0 (newval, "/") == 0)
newval = NULL;
different = !!g_strcmp0 (*param, newval);
if (different) {
g_free (*param);
*param = g_strdup (newval);
}
} else {
success = FALSE;
goto done;
}
} else if (pspec->value_type == G_TYPE_STRV) {
char ***param = (char ***)field;
const char **newval;
gsize i;
newval = g_variant_get_strv (value, NULL);
if (!*param)
different = TRUE;
else {
if (!_nm_utils_strv_equal ((char **) newval, *param)) {
different = TRUE;
g_strfreev (*param);
}
}
if (different) {
for (i = 0; newval[i]; i++)
newval[i] = g_strdup (newval[i]);
*param = (char **) newval;
} else
g_free (newval);
} else if (pspec->value_type == G_TYPE_BYTES) {
GBytes **param = (GBytes **)field;
gconstpointer val, old_val = NULL;
gsize length, old_length = 0;
val = g_variant_get_fixed_array (value, &length, 1);
if (*param)
old_val = g_bytes_get_data (*param, &old_length);
different = old_length != length
|| ( length > 0
&& memcmp (old_val, val, length) != 0);
if (different) {
if (*param)
g_bytes_unref (*param);
*param = length > 0 ? g_bytes_new (val, length) : NULL;
}
} else if (G_IS_PARAM_SPEC_ENUM (pspec)) {
int *param = (int *) field;
int newval = 0;
if (g_variant_is_of_type (value, G_VARIANT_TYPE_INT32))
newval = g_variant_get_int32 (value);
else if (g_variant_is_of_type (value, G_VARIANT_TYPE_UINT32))
newval = g_variant_get_uint32 (value);
else {
success = FALSE;
goto done;
}
different = *param != newval;
*param = newval;
} else if (G_IS_PARAM_SPEC_FLAGS (pspec)) {
guint *param = (guint *) field;
guint newval = 0;
if (g_variant_is_of_type (value, G_VARIANT_TYPE_INT32))
newval = g_variant_get_int32 (value);
else if (g_variant_is_of_type (value, G_VARIANT_TYPE_UINT32))
newval = g_variant_get_uint32 (value);
else {
success = FALSE;
goto done;
}
different = *param != newval;
*param = newval;
} else if (pspec->value_type == G_TYPE_BOOLEAN)
HANDLE_TYPE (G_VARIANT_TYPE_BOOLEAN, gboolean, g_variant_get_boolean);
else if (pspec->value_type == G_TYPE_UCHAR)
HANDLE_TYPE (G_VARIANT_TYPE_BYTE, guchar, g_variant_get_byte);
else if (pspec->value_type == G_TYPE_DOUBLE) {
NM_PRAGMA_WARNING_DISABLE("-Wfloat-equal")
HANDLE_TYPE (G_VARIANT_TYPE_DOUBLE, gdouble, g_variant_get_double);
NM_PRAGMA_WARNING_REENABLE
} else if (pspec->value_type == G_TYPE_INT)
HANDLE_TYPE (G_VARIANT_TYPE_INT32, gint, g_variant_get_int32);
else if (pspec->value_type == G_TYPE_UINT)
HANDLE_TYPE (G_VARIANT_TYPE_UINT32, guint, g_variant_get_uint32);
else if (pspec->value_type == G_TYPE_INT64)
HANDLE_TYPE (G_VARIANT_TYPE_INT64, gint, g_variant_get_int64);
else if (pspec->value_type == G_TYPE_UINT64)
HANDLE_TYPE (G_VARIANT_TYPE_UINT64, guint, g_variant_get_uint64);
else if (pspec->value_type == G_TYPE_LONG)
HANDLE_TYPE (G_VARIANT_TYPE_INT64, glong, g_variant_get_int64);
else if (pspec->value_type == G_TYPE_ULONG)
HANDLE_TYPE (G_VARIANT_TYPE_UINT64, gulong, g_variant_get_uint64);
else {
g_warning ("%s: %s:%s unhandled type %s.",
__func__,
G_OBJECT_TYPE_NAME (object),
pspec->name,
g_type_name (pspec->value_type));
success = FALSE;
}
done:
if (success) {
if (different)
_nm_object_queue_notify (object, pspec->name);
} else {
dbgmsg ("%s: %s:%s (type %s) couldn't be set from D-Bus type %s.",
__func__, G_OBJECT_TYPE_NAME (object), pspec->name,
g_type_name (pspec->value_type), g_variant_get_type_string (value));
}
return success;
}
void
_nm_object_register_properties (NMObject *object,
const char *interface,
const NMPropertiesInfo *info)
{
NMObjectPrivate *priv = NM_OBJECT_GET_PRIVATE (object);
GDBusProxy *proxy;
static gsize dval = 0;
const char *debugstr;
NMPropertiesInfo *tmp;
GHashTable *instance;
g_return_if_fail (NM_IS_OBJECT (object));
g_return_if_fail (interface != NULL);
g_return_if_fail (info != NULL);
if (g_once_init_enter (&dval)) {
debugstr = getenv ("LIBNM_GLIB_DEBUG");
if (debugstr && strstr (debugstr, "properties-changed"))
debug = TRUE;
g_once_init_leave (&dval, 1);
}
proxy = _nm_object_get_proxy (object, interface);
g_signal_connect (proxy, "g-properties-changed",
G_CALLBACK (properties_changed), object);
g_ptr_array_add (priv->proxies, proxy);
instance = g_hash_table_new_full (nm_str_hash, g_str_equal, g_free, g_free);
priv->property_tables = g_slist_prepend (priv->property_tables, instance);
for (tmp = (NMPropertiesInfo *) info; tmp->name; tmp++) {
PropertyInfo *pi;
if (!tmp->name || (tmp->func && !tmp->field)) {
g_warning ("%s: missing field in NMPropertiesInfo", __func__);
continue;
}
pi = g_malloc0 (sizeof (PropertyInfo));
pi->func = tmp->func ? tmp->func : demarshal_generic;
pi->object_type = tmp->object_type;
pi->field = tmp->field;
pi->signal_prefix = tmp->signal_prefix;
g_hash_table_insert (instance, g_strdup (tmp->name), pi);
}
}
void
_nm_object_set_property (NMObject *object,
const char *interface,
const char *prop_name,
const char *format_string,
...)
{
GVariant *val, *ret;
GDBusProxy *proxy;
va_list ap;
g_return_if_fail (NM_IS_OBJECT (object));
g_return_if_fail (interface != NULL);
g_return_if_fail (prop_name != NULL);
g_return_if_fail (format_string != NULL);
va_start (ap, format_string);
val = g_variant_new_va (format_string, NULL, &ap);
va_end (ap);
g_return_if_fail (val != NULL);
proxy = _nm_object_get_proxy (object, interface);
ret = g_dbus_proxy_call_sync (proxy,
DBUS_INTERFACE_PROPERTIES ".Set",
g_variant_new ("(ssv)", interface, prop_name, val),
G_DBUS_CALL_FLAGS_NONE, 2000,
NULL, NULL);
/* Ignore errors. */
if (ret)
g_variant_unref (ret);
g_object_unref (proxy);
}
static void
reload_complete (NMObject *object, gboolean emit_now)
{
NMObjectPrivate *priv = NM_OBJECT_GET_PRIVATE (object);
if (emit_now) {
nm_clear_g_source (&priv->notify_id);
deferred_notify_cb (object);
} else
_nm_object_defer_notify (object);
}
GDBusObjectManager *
_nm_object_get_dbus_object_manager (NMObject *self)
{
return NM_OBJECT_GET_PRIVATE (self)->object_manager;
}
/*****************************************************************************/
static void
init_dbus (NMObject *object)
{
}
static void
init_if (GDBusProxy *proxy, NMObject *self)
{
gchar **props;
char **prop;
GVariant *val;
gchar *str;
nm_assert (G_IS_DBUS_PROXY (proxy));
nm_assert (NM_IS_OBJECT (self));
props = g_dbus_proxy_get_cached_property_names (proxy);
for (prop = props; prop && *prop; prop++) {
val = g_dbus_proxy_get_cached_property (proxy, *prop);
str = g_variant_print (val, TRUE);
handle_property_changed (self, *prop, val);
g_variant_unref (val);
g_free (str);
}
g_strfreev (props);
}
static gboolean
init_sync (GInitable *initable, GCancellable *cancellable, GError **error)
{
NMObject *self = NM_OBJECT (initable);
NMObjectPrivate *priv = NM_OBJECT_GET_PRIVATE (self);
GList *interfaces;
g_assert (priv->object && priv->object_manager);
NM_OBJECT_GET_CLASS (self)->init_dbus (self);
priv->reload_remaining++;
interfaces = g_dbus_object_get_interfaces (priv->object);
g_list_foreach (interfaces, (GFunc) init_if, self);
g_list_free_full (interfaces, g_object_unref);
priv->inited = TRUE;
if (--priv->reload_remaining == 0)
reload_complete (self, TRUE);
/* There are some object properties whose creation couldn't proceed
* because it depended on this object. */
while (priv->waiters) {
ObjectCreatedData *odata = priv->waiters->data;
priv->waiters = g_slist_remove (priv->waiters, odata);
object_property_maybe_complete (odata->self);
}
return TRUE;
}
/*****************************************************************************/
typedef struct {
NMObject *object;
GSimpleAsyncResult *simple;
GCancellable *cancellable;
int proxies_pending;
GError *error;
} NMObjectInitData;
static void
init_async_complete (NMObjectInitData *init_data)
{
if (init_data->error)
g_simple_async_result_take_error (init_data->simple, init_data->error);
else
g_simple_async_result_set_op_res_gboolean (init_data->simple, TRUE);
g_simple_async_result_complete_in_idle (init_data->simple);
g_object_unref (init_data->simple);
g_clear_object (&init_data->cancellable);
g_slice_free (NMObjectInitData, init_data);
}
static void
init_async (GAsyncInitable *initable, int io_priority,
GCancellable *cancellable, GAsyncReadyCallback callback,
gpointer user_data)
{
NMObject *self = NM_OBJECT (initable);
NMObjectPrivate *priv = NM_OBJECT_GET_PRIVATE (self);
NMObjectInitData *init_data;
GList *interfaces;
g_assert (priv->object && priv->object_manager);
NM_OBJECT_GET_CLASS (self)->init_dbus (self);
init_data = g_slice_new0 (NMObjectInitData);
init_data->object = self;
init_data->simple = g_simple_async_result_new (G_OBJECT (initable), callback, user_data, init_async);
init_data->cancellable = cancellable ? g_object_ref (cancellable) : NULL;
interfaces = g_dbus_object_get_interfaces (priv->object);
g_list_foreach (interfaces, (GFunc) init_if, self);
g_list_free_full (interfaces, g_object_unref);
init_async_complete (init_data);
}
static gboolean
init_finish (GAsyncInitable *initable, GAsyncResult *result, GError **error)
{
GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (result);
NMObjectPrivate *priv = NM_OBJECT_GET_PRIVATE (initable);
priv->inited = TRUE;
/* There are some object properties whose creation couldn't proceed
* because it depended on this object. */
while (priv->waiters) {
ObjectCreatedData *odata = priv->waiters->data;
priv->waiters = g_slist_remove (priv->waiters, odata);
object_property_maybe_complete (odata->self);
}
if (g_simple_async_result_propagate_error (simple, error))
return FALSE;
else
return TRUE;
}
/*****************************************************************************/
static void
nm_object_initable_iface_init (GInitableIface *iface)
{
iface->init = init_sync;
}
static void
nm_object_async_initable_iface_init (GAsyncInitableIface *iface)
{
iface->init_async = init_async;
iface->init_finish = init_finish;
}
static void
nm_object_init (NMObject *object)
{
NMObjectPrivate *priv = NM_OBJECT_GET_PRIVATE (object);
c_list_init (&priv->notify_items);
c_list_init (&priv->pending);
priv->proxies = g_ptr_array_new ();
}
static void
set_property (GObject *object, guint prop_id,
const GValue *value, GParamSpec *pspec)
{
NMObjectPrivate *priv = NM_OBJECT_GET_PRIVATE (object);
switch (prop_id) {
case PROP_DBUS_OBJECT:
/* construct-only */
priv->object = g_value_dup_object (value);
if (!priv->object)
g_return_if_reached ();
break;
case PROP_DBUS_OBJECT_MANAGER:
/* construct-only */
priv->object_manager = g_value_dup_object (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
get_property (GObject *object, guint prop_id,
GValue *value, GParamSpec *pspec)
{
NMObjectPrivate *priv = NM_OBJECT_GET_PRIVATE (object);
switch (prop_id) {
case PROP_PATH:
g_value_set_string (value, nm_object_get_path (NM_OBJECT (object)));
break;
case PROP_DBUS_CONNECTION:
g_value_set_object (value, g_dbus_object_manager_client_get_connection (G_DBUS_OBJECT_MANAGER_CLIENT (priv->object_manager)));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
dispose (GObject *object)
{
NMObjectPrivate *priv = NM_OBJECT_GET_PRIVATE (object);
CList *iter, *safe;
guint i;
nm_clear_g_source (&priv->notify_id);
c_list_for_each_safe (iter, safe, &priv->notify_items)
notify_item_free (c_list_entry (iter, NotifyItem, lst));
g_slist_free_full (priv->waiters, odata_free);
g_clear_object (&priv->object);
g_clear_object (&priv->object_manager);
if (priv->proxies) {
for (i = 0; i < priv->proxies->len; i++) {
g_signal_handlers_disconnect_by_func (priv->proxies->pdata[i],
properties_changed,
object);
g_object_unref (priv->proxies->pdata[i]);
}
g_ptr_array_free (priv->proxies, TRUE);
priv->proxies = NULL;
}
G_OBJECT_CLASS (nm_object_parent_class)->dispose (object);
}
static void
finalize (GObject *object)
{
NMObjectPrivate *priv = NM_OBJECT_GET_PRIVATE (object);
g_slist_free_full (priv->property_tables, (GDestroyNotify) g_hash_table_destroy);
G_OBJECT_CLASS (nm_object_parent_class)->finalize (object);
}
static void
nm_object_class_init (NMObjectClass *nm_object_class)
{
GObjectClass *object_class = G_OBJECT_CLASS (nm_object_class);
g_type_class_add_private (nm_object_class, sizeof (NMObjectPrivate));
/* virtual methods */
object_class->set_property = set_property;
object_class->get_property = get_property;
object_class->dispose = dispose;
object_class->finalize = finalize;
nm_object_class->init_dbus = init_dbus;
/* Properties */
/**
* NMObject:path: (skip)
*
* The D-Bus object path.
**/
g_object_class_install_property
(object_class, PROP_PATH,
g_param_spec_string (NM_OBJECT_PATH, "", "",
NULL,
G_PARAM_READABLE |
G_PARAM_STATIC_STRINGS));
/**
* NMObject:dbus-connection: (skip)
*
* The #GDBusConnection of the object.
**/
g_object_class_install_property
(object_class, PROP_DBUS_CONNECTION,
g_param_spec_object (NM_OBJECT_DBUS_CONNECTION, "", "",
G_TYPE_DBUS_CONNECTION,
G_PARAM_READABLE |
G_PARAM_STATIC_STRINGS));
/**
* NMObject:dbus-object: (skip)
*
* The #GDBusObject of the object.
**/
g_object_class_install_property
(object_class, PROP_DBUS_OBJECT,
g_param_spec_object (NM_OBJECT_DBUS_OBJECT, "", "",
G_TYPE_DBUS_OBJECT,
G_PARAM_WRITABLE |
G_PARAM_CONSTRUCT_ONLY |
G_PARAM_STATIC_STRINGS));
/**
* NMObject:dbus-object-manager: (skip)
*
* The #GDBusObjectManager of the object.
**/
g_object_class_install_property
(object_class, PROP_DBUS_OBJECT_MANAGER,
g_param_spec_object (NM_OBJECT_DBUS_OBJECT_MANAGER, "", "",
G_TYPE_DBUS_OBJECT_MANAGER,
G_PARAM_WRITABLE |
G_PARAM_CONSTRUCT_ONLY |
G_PARAM_STATIC_STRINGS));
}