NetworkManager/libnm/nm-object.c
Dan Winship f9f9d297f8 libnm: fix race conditions when creating the same object twice
If two code paths try to asynchronously create the same object at the
same time (eg, because it shows up in two different properties), we
are supposed to deal with that. But at some point a race condition
appeared where we could end up returning a partially-initialized
object for one of the properties in the async case. Fix that.

Also add comments to both the sync and async cases explaining why they
work the way they do.
2014-09-17 08:21:22 -04:00

1411 lines
38 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 <string.h>
#include <gio/gio.h>
#include <glib/gi18n.h>
#include <stdlib.h>
#include <stdio.h>
#include <nm-utils.h>
#include "nm-dbus-interface.h"
#include "nm-object.h"
#include "nm-object-cache.h"
#include "nm-object-private.h"
#include "nm-dbus-glib-types.h"
#include "nm-glib-compat.h"
#include "nm-dbus-helpers-private.h"
static gboolean debug = FALSE;
#define dbgmsg(f,...) if (G_UNLIKELY (debug)) { g_message (f, ## __VA_ARGS__ ); }
static void nm_object_initable_iface_init (GInitableIface *iface);
static void nm_object_async_initable_iface_init (GAsyncInitableIface *iface);
static GHashTable *type_funcs, *type_async_funcs;
G_DEFINE_ABSTRACT_TYPE_WITH_CODE (NMObject, nm_object, G_TYPE_OBJECT,
type_funcs = g_hash_table_new (NULL, NULL);
type_async_funcs = g_hash_table_new (NULL, NULL);
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);
typedef struct {
DBusGConnection *connection;
DBusGProxy *bus_proxy;
gboolean nm_running;
char *path;
DBusGProxy *properties_proxy;
GSList *property_interfaces;
GSList *property_tables;
NMObject *parent;
gboolean suppress_property_updates;
GSList *notify_props;
guint32 notify_id;
GSList *reload_results;
guint reload_remaining;
GError *reload_error;
} NMObjectPrivate;
enum {
PROP_0,
PROP_PATH,
PROP_NM_RUNNING,
LAST_PROP
};
enum {
OBJECT_CREATION_FAILED,
LAST_SIGNAL
};
static guint signals[LAST_SIGNAL] = { 0 };
/**
* nm_object_error_quark:
*
* Registers an error quark for #NMObject if necessary.
*
* Returns: the error quark used for #NMObject errors.
**/
GQuark
nm_object_error_quark (void)
{
static GQuark quark;
if (G_UNLIKELY (!quark))
quark = g_quark_from_static_string ("nm-object-error-quark");
return quark;
}
static void
proxy_name_owner_changed (DBusGProxy *proxy,
const char *name,
const char *old_owner,
const char *new_owner,
gpointer user_data)
{
NMObject *self = NM_OBJECT (user_data);
NMObjectPrivate *priv = NM_OBJECT_GET_PRIVATE (self);
gboolean now_running;
if (g_strcmp0 (name, NM_DBUS_SERVICE) != 0)
return;
now_running = (new_owner && new_owner[0]);
if (now_running != priv->nm_running) {
priv->nm_running = now_running;
g_object_notify (G_OBJECT (self), NM_OBJECT_NM_RUNNING);
}
}
static void
nm_object_init (NMObject *object)
{
}
static gboolean
init_common (NMObject *self, GError **error)
{
NMObjectPrivate *priv = NM_OBJECT_GET_PRIVATE (self);
if (!priv->path) {
g_set_error_literal (error, NM_OBJECT_ERROR, NM_OBJECT_ERROR_OBJECT_CREATION_FAILURE,
_("Caller did not specify D-Bus path for object"));
return FALSE;
}
NM_OBJECT_GET_CLASS (self)->init_dbus (self);
return TRUE;
}
static void
init_dbus (NMObject *object)
{
NMObjectPrivate *priv = NM_OBJECT_GET_PRIVATE (object);
priv->properties_proxy = _nm_object_new_proxy (object, NULL, "org.freedesktop.DBus.Properties");
if (_nm_dbus_is_connection_private (priv->connection))
priv->nm_running = TRUE;
else {
priv->bus_proxy = dbus_g_proxy_new_for_name (priv->connection,
DBUS_SERVICE_DBUS,
DBUS_PATH_DBUS,
DBUS_INTERFACE_DBUS);
g_assert (priv->bus_proxy);
dbus_g_proxy_add_signal (priv->bus_proxy, "NameOwnerChanged",
G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING,
G_TYPE_INVALID);
dbus_g_proxy_connect_signal (priv->bus_proxy,
"NameOwnerChanged",
G_CALLBACK (proxy_name_owner_changed),
object, NULL);
}
}
static gboolean
init_sync (GInitable *initable, GCancellable *cancellable, GError **error)
{
NMObject *self = NM_OBJECT (initable);
NMObjectPrivate *priv = NM_OBJECT_GET_PRIVATE (self);
priv->connection = _nm_dbus_new_connection (error);
if (!priv->connection)
return FALSE;
if (!init_common (self, error))
return FALSE;
if (priv->bus_proxy) {
if (!dbus_g_proxy_call (priv->bus_proxy,
"NameHasOwner", error,
G_TYPE_STRING, NM_DBUS_SERVICE,
G_TYPE_INVALID,
G_TYPE_BOOLEAN, &priv->nm_running,
G_TYPE_INVALID))
return FALSE;
}
return _nm_object_reload_properties (self, error);
}
/* Takes ownership of @error */
static void
init_async_complete (GSimpleAsyncResult *simple, GError *error)
{
if (error)
g_simple_async_result_take_error (simple, error);
else
g_simple_async_result_set_op_res_gboolean (simple, TRUE);
g_simple_async_result_complete (simple);
g_object_unref (simple);
}
static void
init_async_got_properties (GObject *object, GAsyncResult *result, gpointer user_data)
{
GSimpleAsyncResult *simple = user_data;
GError *error = NULL;
if (!_nm_object_reload_properties_finish (NM_OBJECT (object), result, &error))
g_assert (error);
init_async_complete (simple, error);
}
static void
init_async_got_nm_running (DBusGProxy *proxy, DBusGProxyCall *call,
gpointer user_data)
{
GSimpleAsyncResult *simple = user_data;
NMObject *self;
NMObjectPrivate *priv;
GError *error = NULL;
self = NM_OBJECT (g_async_result_get_source_object (G_ASYNC_RESULT (simple)));
priv = NM_OBJECT_GET_PRIVATE (self);
if (!dbus_g_proxy_end_call (proxy, call, &error,
G_TYPE_BOOLEAN, &priv->nm_running,
G_TYPE_INVALID)) {
init_async_complete (simple, error);
} else if (!priv->nm_running)
init_async_complete (simple, NULL);
else
_nm_object_reload_properties_async (self, init_async_got_properties, simple);
/* g_async_result_get_source_object() adds a ref */
g_object_unref (self);
}
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);
GSimpleAsyncResult *simple;
GError *error = NULL;
simple = g_simple_async_result_new (G_OBJECT (initable), callback, user_data, init_async);
priv->connection = _nm_dbus_new_connection (&error);
if (!priv->connection) {
g_simple_async_result_take_error (simple, error);
g_simple_async_result_complete_in_idle (simple);
g_object_unref (simple);
return;
}
if (!init_common (self, &error)) {
g_simple_async_result_take_error (simple, error);
g_simple_async_result_complete_in_idle (simple);
g_object_unref (simple);
return;
}
if (priv->bus_proxy) {
/* Check if NM is running */
dbus_g_proxy_begin_call (priv->bus_proxy, "NameHasOwner",
init_async_got_nm_running,
simple, NULL,
G_TYPE_STRING, NM_DBUS_SERVICE,
G_TYPE_INVALID);
} else
_nm_object_reload_properties_async (self, init_async_got_properties, simple);
}
static gboolean
init_finish (GAsyncInitable *initable, GAsyncResult *result, GError **error)
{
GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (result);
if (g_simple_async_result_propagate_error (simple, error))
return FALSE;
else
return TRUE;
}
static void
dispose (GObject *object)
{
NMObjectPrivate *priv = NM_OBJECT_GET_PRIVATE (object);
if (priv->notify_id) {
g_source_remove (priv->notify_id);
priv->notify_id = 0;
}
g_slist_free_full (priv->notify_props, g_free);
priv->notify_props = NULL;
g_slist_free_full (priv->property_interfaces, g_free);
priv->property_interfaces = NULL;
g_clear_object (&priv->properties_proxy);
g_clear_object (&priv->bus_proxy);
if (priv->connection) {
dbus_g_connection_unref (priv->connection);
priv->connection = 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_free (priv->path);
G_OBJECT_CLASS (nm_object_parent_class)->finalize (object);
}
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_PATH:
/* Construct only */
priv->path = g_value_dup_string (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, priv->path);
break;
case PROP_NM_RUNNING:
g_value_set_boolean (value, priv->nm_running);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
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:
*
* The D-Bus object path.
**/
g_object_class_install_property
(object_class, PROP_PATH,
g_param_spec_string (NM_OBJECT_PATH, "", "",
NULL,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT_ONLY |
G_PARAM_STATIC_STRINGS));
/**
* NMObject:manager-running: (skip)
*
* Internal use only.
*/
g_object_class_install_property
(object_class, PROP_NM_RUNNING,
g_param_spec_boolean (NM_OBJECT_NM_RUNNING, "", "",
FALSE,
G_PARAM_READABLE |
G_PARAM_STATIC_STRINGS));
/* signals */
/**
* NMObject::object-creation-failed:
* @master_object: the object that received the signal
* @error: the error that occured while creating object
* @failed_path: object path of the failed object
*
* Indicates that an error occured while creating an #NMObject object
* during property handling of @master_object.
*
* Note: Be aware that the signal is private for libnm's internal
* use.
**/
signals[OBJECT_CREATION_FAILED] =
g_signal_new ("object-creation-failed",
G_OBJECT_CLASS_TYPE (object_class),
G_SIGNAL_RUN_FIRST,
G_STRUCT_OFFSET (NMObjectClass, object_creation_failed),
NULL, NULL, NULL,
G_TYPE_NONE, 2, G_TYPE_POINTER, G_TYPE_POINTER);
}
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;
}
/**
* 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
* device, and must not be modified.
**/
const char *
nm_object_get_path (NMObject *object)
{
g_return_val_if_fail (NM_IS_OBJECT (object), NULL);
return NM_OBJECT_GET_PRIVATE (object)->path;
}
static gboolean
deferred_notify_cb (gpointer data)
{
NMObject *object = NM_OBJECT (data);
NMObjectPrivate *priv = NM_OBJECT_GET_PRIVATE (object);
GSList *props, *iter;
priv->notify_id = 0;
/* Clear priv->notify_props 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.
*/
props = g_slist_reverse (priv->notify_props);
priv->notify_props = NULL;
g_object_ref (object);
for (iter = props; iter; iter = g_slist_next (iter)) {
g_object_notify (G_OBJECT (object), (const char *) iter->data);
g_free (iter->data);
}
g_object_unref (object);
g_slist_free (props);
return FALSE;
}
void
_nm_object_queue_notify (NMObject *object, const char *property)
{
NMObjectPrivate *priv;
gboolean found = FALSE;
GSList *iter;
g_return_if_fail (NM_IS_OBJECT (object));
g_return_if_fail (property != NULL);
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);
for (iter = priv->notify_props; iter; iter = g_slist_next (iter)) {
if (!strcmp ((char *) iter->data, property)) {
found = TRUE;
break;
}
}
if (!found)
priv->notify_props = g_slist_prepend (priv->notify_props, g_strdup (property));
}
void
_nm_object_register_type_func (GType base_type, NMObjectTypeFunc type_func,
NMObjectTypeAsyncFunc type_async_func)
{
g_hash_table_insert (type_funcs,
GSIZE_TO_POINTER (base_type),
type_func);
g_hash_table_insert (type_async_funcs,
GSIZE_TO_POINTER (base_type),
type_async_func);
}
static GObject *
_nm_object_create (GType type, DBusGConnection *connection, const char *path)
{
NMObjectTypeFunc type_func;
GObject *object;
GError *error = NULL;
type_func = g_hash_table_lookup (type_funcs, GSIZE_TO_POINTER (type));
if (type_func)
type = type_func (connection, path);
if (type == G_TYPE_INVALID) {
dbgmsg ("Could not create object for %s: unknown object type", path);
return NULL;
}
object = g_object_new (type,
NM_OBJECT_PATH, path,
NULL);
/* Cache the object before initializing it (and in particular, loading its
* property values); this is necessary to make circular references work (eg,
* when creating an NMActiveConnection, it will create an NMDevice which
* will in turn try to create the parent NMActiveConnection). Since we don't
* support multi-threaded use, we know that we will have inited the object
* before any external code sees it.
*/
_nm_object_cache_add (NM_OBJECT (object));
if (!g_initable_init (G_INITABLE (object), NULL, &error)) {
dbgmsg ("Could not create object for %s: %s", path, error->message);
g_error_free (error);
g_clear_object (&object);
}
return object;
}
typedef void (*NMObjectCreateCallbackFunc) (GObject *, const char *, gpointer);
typedef struct {
char *path;
NMObjectCreateCallbackFunc callback;
gpointer user_data;
} NMObjectTypeAsyncData;
static void
create_async_complete (GObject *object, NMObjectTypeAsyncData *async_data)
{
async_data->callback (object, async_data->path, async_data->user_data);
g_free (async_data->path);
g_slice_free (NMObjectTypeAsyncData, async_data);
}
static void
async_inited (GObject *object, GAsyncResult *result, gpointer user_data)
{
NMObjectTypeAsyncData *async_data = user_data;
GError *error = NULL;
if (g_async_initable_init_finish (G_ASYNC_INITABLE (object), result, &error)) {
NMObject *cached;
/* Unlike in the sync case, in the async case we can't cache the object
* until it is completely initialized. But that means someone else might
* have been creating it at the same time, and they might have finished
* before us.
*/
cached = _nm_object_cache_get (nm_object_get_path (NM_OBJECT (object)));
if (cached) {
g_object_unref (object);
object = G_OBJECT (cached);
} else
_nm_object_cache_add (NM_OBJECT (object));
} else {
dbgmsg ("Could not create object for %s: %s",
nm_object_get_path (NM_OBJECT (object)),
error->message);
g_error_free (error);
g_clear_object (&object);
}
create_async_complete (object, async_data);
}
static void
async_got_type (GType type, gpointer user_data)
{
NMObjectTypeAsyncData *async_data = user_data;
GObject *object;
if (type == G_TYPE_INVALID) {
/* Don't know how to create this object */
create_async_complete (NULL, async_data);
return;
}
object = g_object_new (type,
NM_OBJECT_PATH, async_data->path,
NULL);
g_async_initable_init_async (G_ASYNC_INITABLE (object), G_PRIORITY_DEFAULT,
NULL, async_inited, async_data);
}
static void
_nm_object_create_async (GType type, DBusGConnection *connection, const char *path,
NMObjectCreateCallbackFunc callback, gpointer user_data)
{
NMObjectTypeAsyncFunc type_async_func;
NMObjectTypeFunc type_func;
NMObjectTypeAsyncData *async_data;
async_data = g_slice_new (NMObjectTypeAsyncData);
async_data->path = g_strdup (path);
async_data->callback = callback;
async_data->user_data = user_data;
type_async_func = g_hash_table_lookup (type_async_funcs, GSIZE_TO_POINTER (type));
if (type_async_func) {
type_async_func (connection, path, async_got_type, async_data);
return;
}
type_func = g_hash_table_lookup (type_funcs, GSIZE_TO_POINTER (type));
if (type_func)
type = type_func (connection, path);
async_got_type (type, async_data);
}
/* 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);
}
}
typedef struct {
NMObject *self;
PropertyInfo *pi;
GObject **objects;
int length, remaining;
gboolean array;
const char *property_name;
} ObjectCreatedData;
/* 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
emit_added_removed_signal (NMObject *self,
const char *signal_prefix,
NMObject *changed,
gboolean added)
{
char buf[50];
int ret;
ret = g_snprintf (buf, sizeof (buf), "%s-%s", signal_prefix, added ? "added" : "removed");
g_assert (ret < sizeof (buf));
g_signal_emit_by_name (self, buf, changed);
}
static void
object_property_complete (ObjectCreatedData *odata)
{
NMObject *self = odata->self;
NMObjectPrivate *priv = NM_OBJECT_GET_PRIVATE (self);
PropertyInfo *pi = odata->pi;
gboolean different = TRUE;
if (odata->array) {
GPtrArray *old = *((GPtrArray **) pi->field);
GPtrArray *new;
int i;
/* 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]);
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));
}
*((GPtrArray **) pi->field) = new;
/* Emit added & removed */
for (i = 0; i < removed->len; i++) {
emit_added_removed_signal (self,
pi->signal_prefix,
g_ptr_array_index (removed, i),
FALSE);
}
for (i = 0; i < added->len; i++) {
emit_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.
*/
*((GPtrArray **) pi->field) = new;
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_results && --priv->reload_remaining == 0)
reload_complete (self);
g_object_unref (self);
g_free (odata->objects);
g_slice_free (ObjectCreatedData, 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 ) {
GError *error;
error = g_error_new (NM_OBJECT_ERROR,
NM_OBJECT_ERROR_OBJECT_CREATION_FAILURE,
"Creating object for path '%s' failed in libnm.",
path);
/* Emit a signal about the error. */
g_signal_emit (odata->self, signals[OBJECT_CREATION_FAILED], 0, error, path);
g_error_free (error);
}
odata->objects[--odata->remaining] = obj;
if (!odata->remaining)
object_property_complete (odata);
}
static gboolean
handle_object_property (NMObject *self, const char *property_name, GValue *value,
PropertyInfo *pi, gboolean synchronously)
{
NMObjectPrivate *priv = NM_OBJECT_GET_PRIVATE (self);
GObject *obj;
const char *path;
ObjectCreatedData *odata;
odata = g_slice_new (ObjectCreatedData);
odata->self = g_object_ref (self);
odata->pi = pi;
odata->objects = g_new (GObject *, 1);
odata->length = odata->remaining = 1;
odata->array = FALSE;
odata->property_name = property_name;
if (priv->reload_results)
priv->reload_remaining++;
path = g_value_get_boxed (value);
if (!strcmp (path, "/")) {
object_created (NULL, path, odata);
return TRUE;
}
obj = G_OBJECT (_nm_object_cache_get (path));
if (obj) {
object_created (obj, path, odata);
return TRUE;
} else if (synchronously) {
obj = _nm_object_create (pi->object_type, priv->connection, path);
object_created (obj, path, odata);
return obj != NULL;
} else {
_nm_object_create_async (pi->object_type, priv->connection, path,
object_created, odata);
/* Assume success */
return TRUE;
}
}
static gboolean
handle_object_array_property (NMObject *self, const char *property_name, GValue *value,
PropertyInfo *pi, gboolean synchronously)
{
NMObjectPrivate *priv = NM_OBJECT_GET_PRIVATE (self);
GObject *obj;
GPtrArray *paths;
GPtrArray **array = pi->field;
const char *path;
ObjectCreatedData *odata;
int i;
paths = g_value_get_boxed (value);
odata = g_slice_new (ObjectCreatedData);
odata->self = g_object_ref (self);
odata->pi = pi;
odata->objects = g_new0 (GObject *, paths->len);
odata->length = odata->remaining = paths->len;
odata->array = TRUE;
odata->property_name = property_name;
if (priv->reload_results)
priv->reload_remaining++;
if (paths->len == 0) {
object_property_complete (odata);
return TRUE;
}
for (i = 0; i < paths->len; i++) {
path = paths->pdata[i];
if (!strcmp (path, "/")) {
/* FIXME: can't happen? */
continue;
}
obj = G_OBJECT (_nm_object_cache_get (path));
if (obj) {
object_created (obj, path, odata);
} else if (synchronously) {
obj = _nm_object_create (pi->object_type, priv->connection, path);
object_created (obj, path, odata);
} else {
_nm_object_create_async (pi->object_type, priv->connection, path,
object_created, odata);
}
}
if (!synchronously) {
/* Assume success */
return TRUE;
}
return *array && ((*array)->len == paths->len);
}
static void
handle_property_changed (NMObject *self, const char *dbus_name, GValue *value, gboolean synchronously)
{
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) {
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_strdup_value_contents (value);
dbgmsg ("PC: (%p) %s::%s => '%s' (%s%s%s)",
self, G_OBJECT_TYPE_NAME (self),
prop_name,
s,
G_VALUE_TYPE_NAME (value),
pi->object_type ? " / " : "",
pi->object_type ? g_type_name (pi->object_type) : "");
g_free (s);
}
if (pi->object_type) {
if (G_VALUE_HOLDS (value, DBUS_TYPE_G_OBJECT_PATH))
success = handle_object_property (self, pspec->name, value, pi, synchronously);
else if (G_VALUE_HOLDS (value, DBUS_TYPE_G_ARRAY_OF_OBJECT_PATH))
success = handle_object_array_property (self, pspec->name, value, pi, synchronously);
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
process_properties_changed (NMObject *self, GHashTable *properties, gboolean synchronously)
{
NMObjectPrivate *priv = NM_OBJECT_GET_PRIVATE (self);
GHashTableIter iter;
gpointer name, value;
if (priv->suppress_property_updates)
return;
g_hash_table_iter_init (&iter, properties);
while (g_hash_table_iter_next (&iter, &name, &value)) {
if (value)
handle_property_changed (self, name, value, synchronously);
else {
dbgmsg ("%s:%d %s(): object %s property '%s' value is unexpectedly NULL",
__FILE__, __LINE__, __func__, G_OBJECT_TYPE_NAME (self), (const char *) name);
}
}
}
static void
properties_changed_proxy (DBusGProxy *proxy,
GHashTable *properties,
gpointer user_data)
{
process_properties_changed (NM_OBJECT (user_data), properties, FALSE);
}
#define HANDLE_TYPE(ucase, lcase, getter) \
} else if (pspec->value_type == G_TYPE_##ucase) { \
if (G_VALUE_HOLDS_##ucase (value)) { \
g##lcase *param = (g##lcase *) field; \
*param = g_value_get_##getter (value); \
} else { \
success = FALSE; \
goto done; \
}
static gboolean
demarshal_generic (NMObject *object,
GParamSpec *pspec,
GValue *value,
gpointer field)
{
gboolean success = TRUE;
if (pspec->value_type == G_TYPE_STRING) {
if (G_VALUE_HOLDS_STRING (value)) {
char **param = (char **) field;
g_free (*param);
*param = g_value_dup_string (value);
} else if (G_VALUE_HOLDS (value, DBUS_TYPE_G_OBJECT_PATH)) {
char **param = (char **) field;
g_free (*param);
*param = g_strdup (g_value_get_boxed (value));
/* Handle "NULL" object paths */
if (g_strcmp0 (*param, "/") == 0) {
g_free (*param);
*param = NULL;
}
} else {
success = FALSE;
goto done;
}
} else if (pspec->value_type == G_TYPE_STRV) {
char ***param = (char ***)field;
if (*param)
g_strfreev (*param);
*param = g_value_dup_boxed (value);
} else if (pspec->value_type == G_TYPE_BYTES) {
GBytes **param = (GBytes **)field;
GByteArray *val;
if (*param)
g_bytes_unref (*param);
val = g_value_get_boxed (value);
*param = g_bytes_new (val->data, val->len);
HANDLE_TYPE(BOOLEAN, boolean, boolean)
HANDLE_TYPE(CHAR, char, schar)
HANDLE_TYPE(UCHAR, uchar, uchar)
HANDLE_TYPE(DOUBLE, double, double)
HANDLE_TYPE(INT, int, int)
HANDLE_TYPE(UINT, uint, uint)
HANDLE_TYPE(INT64, int, int)
HANDLE_TYPE(UINT64, uint, uint)
HANDLE_TYPE(LONG, long, long)
HANDLE_TYPE(ULONG, ulong, ulong)
} else {
dbgmsg ("%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) {
_nm_object_queue_notify (object, pspec->name);
} else {
dbgmsg ("%s: %s/%s (type %s) couldn't be set with type %s.",
__func__, G_OBJECT_TYPE_NAME (object), pspec->name,
g_type_name (pspec->value_type), G_VALUE_TYPE_NAME (value));
}
return success;
}
void
_nm_object_register_properties (NMObject *object,
DBusGProxy *proxy,
const NMPropertiesInfo *info)
{
NMObjectPrivate *priv = NM_OBJECT_GET_PRIVATE (object);
static gsize dval = 0;
const char *debugstr;
NMPropertiesInfo *tmp;
GHashTable *instance;
g_return_if_fail (NM_IS_OBJECT (object));
g_return_if_fail (proxy != 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);
}
priv->property_interfaces = g_slist_prepend (priv->property_interfaces,
g_strdup (dbus_g_proxy_get_interface (proxy)));
dbus_g_proxy_add_signal (proxy, "PropertiesChanged", DBUS_TYPE_G_MAP_OF_VARIANT, G_TYPE_INVALID);
dbus_g_proxy_connect_signal (proxy,
"PropertiesChanged",
G_CALLBACK (properties_changed_proxy),
object,
NULL);
instance = g_hash_table_new_full (g_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);
}
}
gboolean
_nm_object_reload_properties (NMObject *object, GError **error)
{
NMObjectPrivate *priv = NM_OBJECT_GET_PRIVATE (object);
GHashTable *props = NULL;
GSList *p;
if (!priv->property_interfaces || !priv->nm_running)
return TRUE;
for (p = priv->property_interfaces; p; p = p->next) {
if (!dbus_g_proxy_call (priv->properties_proxy, "GetAll", error,
G_TYPE_STRING, p->data,
G_TYPE_INVALID,
DBUS_TYPE_G_MAP_OF_VARIANT, &props,
G_TYPE_INVALID))
return FALSE;
process_properties_changed (object, props, TRUE);
g_hash_table_destroy (props);
}
return TRUE;
}
void
_nm_object_suppress_property_updates (NMObject *object, gboolean suppress)
{
NMObjectPrivate *priv = NM_OBJECT_GET_PRIVATE (object);
priv->suppress_property_updates = suppress;
}
void
_nm_object_reload_property (NMObject *object,
const char *interface,
const char *prop_name)
{
GValue value = G_VALUE_INIT;
GError *err = NULL;
g_return_if_fail (NM_IS_OBJECT (object));
g_return_if_fail (interface != NULL);
g_return_if_fail (prop_name != NULL);
if (!NM_OBJECT_GET_PRIVATE (object)->nm_running)
return;
if (!dbus_g_proxy_call_with_timeout (NM_OBJECT_GET_PRIVATE (object)->properties_proxy,
"Get", 15000, &err,
G_TYPE_STRING, interface,
G_TYPE_STRING, prop_name,
G_TYPE_INVALID,
G_TYPE_VALUE, &value,
G_TYPE_INVALID)) {
dbgmsg ("%s: Error getting '%s' for %s: (%d) %s\n",
__func__,
prop_name,
nm_object_get_path (object),
err->code,
err->message);
g_clear_error (&err);
return;
}
handle_property_changed (object, prop_name, &value, TRUE);
g_value_unset (&value);
}
void
_nm_object_set_property (NMObject *object,
const char *interface,
const char *prop_name,
GValue *value)
{
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 (G_IS_VALUE (value));
if (!NM_OBJECT_GET_PRIVATE (object)->nm_running)
return;
if (!dbus_g_proxy_call_with_timeout (NM_OBJECT_GET_PRIVATE (object)->properties_proxy,
"Set", 2000, NULL,
G_TYPE_STRING, interface,
G_TYPE_STRING, prop_name,
G_TYPE_VALUE, value,
G_TYPE_INVALID)) {
/* Ignore errors. dbus_g_proxy_call_with_timeout() is called instead of
* dbus_g_proxy_call_no_reply() to give NM chance to authenticate the caller.
*/
}
}
static void
reload_complete (NMObject *object)
{
NMObjectPrivate *priv = NM_OBJECT_GET_PRIVATE (object);
GSimpleAsyncResult *simple;
GSList *results, *iter;
GError *error;
results = priv->reload_results;
priv->reload_results = NULL;
error = priv->reload_error;
priv->reload_error = NULL;
for (iter = results; iter; iter = iter->next) {
simple = iter->data;
if (error)
g_simple_async_result_set_from_error (simple, error);
else
g_simple_async_result_set_op_res_gboolean (simple, TRUE);
g_simple_async_result_complete (simple);
g_object_unref (simple);
}
g_slist_free (results);
g_clear_error (&error);
}
static void
reload_got_properties (DBusGProxy *proxy, DBusGProxyCall *call,
gpointer user_data)
{
NMObject *object = user_data;
NMObjectPrivate *priv = NM_OBJECT_GET_PRIVATE (object);
GHashTable *props = NULL;
GError *error = NULL;
if (dbus_g_proxy_end_call (proxy, call, &error,
DBUS_TYPE_G_MAP_OF_VARIANT, &props,
G_TYPE_INVALID)) {
process_properties_changed (object, props, FALSE);
g_hash_table_destroy (props);
} else {
if (priv->reload_error)
g_error_free (error);
else
priv->reload_error = error;
}
if (--priv->reload_remaining == 0)
reload_complete (object);
}
void
_nm_object_reload_properties_async (NMObject *object, GAsyncReadyCallback callback, gpointer user_data)
{
NMObjectPrivate *priv = NM_OBJECT_GET_PRIVATE (object);
GSimpleAsyncResult *simple;
GSList *p;
simple = g_simple_async_result_new (G_OBJECT (object), callback,
user_data, _nm_object_reload_properties_async);
if (!priv->property_interfaces) {
g_simple_async_result_complete_in_idle (simple);
g_object_unref (simple);
return;
}
priv->reload_results = g_slist_prepend (priv->reload_results, simple);
/* If there was already a reload happening, we don't need to
* re-read the properties again, we just need to wait for the
* existing reload to finish.
*/
if (priv->reload_results->next)
return;
for (p = priv->property_interfaces; p; p = p->next) {
priv->reload_remaining++;
dbus_g_proxy_begin_call (priv->properties_proxy, "GetAll",
reload_got_properties, object, NULL,
G_TYPE_STRING, p->data,
G_TYPE_INVALID);
}
}
gboolean
_nm_object_reload_properties_finish (NMObject *object, GAsyncResult *result, GError **error)
{
GSimpleAsyncResult *simple;
g_return_val_if_fail (NM_IS_OBJECT (object), FALSE);
g_return_val_if_fail (g_simple_async_result_is_valid (result, G_OBJECT (object), _nm_object_reload_properties_async), FALSE);
simple = G_SIMPLE_ASYNC_RESULT (result);
if (g_simple_async_result_propagate_error (simple, error))
return FALSE;
return g_simple_async_result_get_op_res_gboolean (simple);
}
DBusGProxy *
_nm_object_new_proxy (NMObject *self, const char *path, const char *interface)
{
NMObjectPrivate *priv = NM_OBJECT_GET_PRIVATE (self);
return _nm_dbus_new_proxy_for_connection (priv->connection, path ? path : priv->path, interface);
}
gboolean
_nm_object_get_nm_running (NMObject *self)
{
return NM_OBJECT_GET_PRIVATE (self)->nm_running;
}