Files
NetworkManager/libnm-glib/libnm_glib.c
Thomas Haller 3eb1d5e902 core: cleanup freeing of glib collections of pointers
When freeing one of the collections such as GArray, GPtrArray, GSList,
etc. it is common that the items inside the connections must be
freed/unrefed too.

The previous code often iterated over the collection first with
e.g. g_ptr_array_foreach and passing e.g. g_free as GFunc argument.
For one, this has the problem, that g_free has a different signature
GDestroyNotify then the expected GFunc. Moreover, this can be
simplified either by setting a clear function
(g_ptr_array_set_clear_func) or by passing the destroy function to the
free function (g_slist_free_full).

Signed-off-by: Thomas Haller <thaller@redhat.com>
2013-10-22 19:53:57 +02:00

596 lines
15 KiB
C

/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
/*
* libnm_glib -- Access network status & information from glib applications
*
* 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 (C) 2005 - 2008 Red Hat, Inc.
* Copyright (C) 2005 - 2008 Novell, Inc.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <glib.h>
#include <dbus/dbus.h>
#include <dbus/dbus-glib-lowlevel.h>
#include "NetworkManager.h"
#include "libnm_glib.h"
#define DBUS_NO_SERVICE_ERROR "org.freedesktop.DBus.Error.ServiceDoesNotExist"
struct libnm_glib_ctx
{
unsigned char check;
GMainContext * g_main_ctx;
GMainLoop * g_main_loop;
DBusConnection * dbus_con;
guint dbus_watcher;
guint dbus_watch_interval;
gboolean thread_done;
gboolean thread_inited;
GThread * thread;
GSList * callbacks;
GMutex * callbacks_lock;
guint callback_id_last;
libnm_glib_state nm_state;
};
typedef struct libnm_glib_callback
{
guint id;
GMainContext * gmain_ctx;
libnm_glib_ctx * libnm_glib_ctx;
libnm_glib_callback_func func;
gpointer user_data;
} libnm_glib_callback;
static void _libnm_glib_schedule_dbus_watcher (libnm_glib_ctx *ctx);
static DBusConnection * _libnm_glib_dbus_init (gpointer *user_data, GMainContext *context);
static void _libnm_glib_update_state (libnm_glib_ctx *ctx, NMState state);
static void
_libnm_glib_nm_state_cb (DBusPendingCall *pcall, void *user_data)
{
DBusMessage * reply;
libnm_glib_ctx * ctx = (libnm_glib_ctx *) user_data;
NMState nm_state;
g_return_if_fail (pcall != NULL);
g_return_if_fail (ctx != NULL);
if (!(reply = dbus_pending_call_steal_reply (pcall)))
goto out;
if (dbus_message_get_type (reply) == DBUS_MESSAGE_TYPE_ERROR)
{
DBusError err;
dbus_error_init (&err);
dbus_set_error_from_message (&err, reply);
fprintf (stderr, "%s: dbus returned an error.\n (%s) %s\n", __func__, err.name, err.message);
dbus_error_free (&err);
dbus_message_unref (reply);
goto out;
}
if (dbus_message_get_args (reply, NULL, DBUS_TYPE_UINT32, &nm_state, DBUS_TYPE_INVALID))
_libnm_glib_update_state (ctx, nm_state);
dbus_message_unref (reply);
out:
dbus_pending_call_unref (pcall);
}
static void
_libnm_glib_get_nm_state (libnm_glib_ctx *ctx)
{
DBusMessage * message;
DBusPendingCall * pcall = NULL;
g_return_if_fail (ctx != NULL);
if ((message = dbus_message_new_method_call (NM_DBUS_SERVICE, NM_DBUS_PATH, NM_DBUS_INTERFACE, "state")))
{
dbus_connection_send_with_reply (ctx->dbus_con, message, &pcall, -1);
if (pcall)
dbus_pending_call_set_notify (pcall, _libnm_glib_nm_state_cb, ctx, NULL);
dbus_message_unref (message);
}
}
static gboolean
_libnm_glib_callback_helper (gpointer user_data)
{
libnm_glib_callback *cb_data = (libnm_glib_callback *)user_data;
g_return_val_if_fail (cb_data != NULL, FALSE);
g_return_val_if_fail (cb_data->func != NULL, FALSE);
g_return_val_if_fail (cb_data->libnm_glib_ctx != NULL, FALSE);
(*(cb_data->func)) (cb_data->libnm_glib_ctx, cb_data->user_data);
return FALSE;
}
static void
_libnm_glib_schedule_single_callback (libnm_glib_ctx *ctx,
libnm_glib_callback *callback)
{
GSource *source;
g_return_if_fail (ctx != NULL);
g_return_if_fail (callback != NULL);
callback->libnm_glib_ctx = ctx;
source = g_idle_source_new ();
g_source_set_callback (source, _libnm_glib_callback_helper, callback, NULL);
g_source_attach (source, callback->gmain_ctx);
g_source_unref (source);
}
static void
_libnm_glib_unschedule_single_callback (libnm_glib_ctx *ctx,
libnm_glib_callback *callback)
{
GSource *source;
g_return_if_fail (ctx != NULL);
g_return_if_fail (callback != NULL);
source = g_main_context_find_source_by_user_data (callback->gmain_ctx, callback);
if (source)
g_source_destroy (source);
}
static void
_libnm_glib_call_callbacks (libnm_glib_ctx *ctx)
{
GSList *elem;
g_return_if_fail (ctx != NULL);
g_mutex_lock (ctx->callbacks_lock);
for (elem = ctx->callbacks; elem; elem = g_slist_next (elem))
{
libnm_glib_callback *callback = (libnm_glib_callback *)(elem->data);
if (callback)
_libnm_glib_schedule_single_callback (ctx, callback);
}
g_mutex_unlock (ctx->callbacks_lock);
}
static void
_libnm_glib_update_state (libnm_glib_ctx *ctx, NMState state)
{
libnm_glib_state old_state;
g_return_if_fail (ctx != NULL);
old_state = ctx->nm_state;
switch (state) {
case NM_STATE_CONNECTED_LOCAL:
case NM_STATE_CONNECTED_SITE:
case NM_STATE_CONNECTED_GLOBAL:
ctx->nm_state = LIBNM_ACTIVE_NETWORK_CONNECTION;
break;
case NM_STATE_ASLEEP:
case NM_STATE_CONNECTING:
case NM_STATE_DISCONNECTED:
case NM_STATE_DISCONNECTING:
ctx->nm_state = LIBNM_NO_NETWORK_CONNECTION;
break;
case NM_STATE_UNKNOWN:
default:
ctx->nm_state = LIBNM_NO_NETWORKMANAGER;
break;
}
if (old_state != ctx->nm_state)
_libnm_glib_call_callbacks (ctx);
}
static DBusHandlerResult
_libnm_glib_dbus_filter (DBusConnection *connection,
DBusMessage *message,
void *user_data)
{
libnm_glib_ctx *ctx = (libnm_glib_ctx *)user_data;
gboolean handled = TRUE;
DBusError error;
g_return_val_if_fail (ctx != NULL, DBUS_HANDLER_RESULT_NOT_YET_HANDLED);
g_return_val_if_fail (connection != NULL, DBUS_HANDLER_RESULT_NOT_YET_HANDLED);
g_return_val_if_fail (message != NULL, DBUS_HANDLER_RESULT_NOT_YET_HANDLED);
dbus_error_init (&error);
if (dbus_message_is_signal (message, DBUS_INTERFACE_LOCAL, "Disconnected"))
{
/* Try to reactivate our connection to dbus on the next pass through the event loop */
ctx->nm_state = LIBNM_NO_DBUS;
dbus_connection_close (ctx->dbus_con);
dbus_connection_unref (ctx->dbus_con);
ctx->dbus_con = NULL;
_libnm_glib_schedule_dbus_watcher (ctx);
}
else if (dbus_message_is_signal (message, DBUS_INTERFACE_DBUS, "NameOwnerChanged"))
{
/* New signal for dbus 0.23... */
char *service;
char *old_owner;
char *new_owner;
if ( dbus_message_get_args (message, &error,
DBUS_TYPE_STRING, &service,
DBUS_TYPE_STRING, &old_owner,
DBUS_TYPE_STRING, &new_owner,
DBUS_TYPE_INVALID))
{
if (strcmp (service, NM_DBUS_SERVICE) == 0)
{
gboolean old_owner_good = (old_owner && (strlen (old_owner) > 0));
gboolean new_owner_good = (new_owner && (strlen (new_owner) > 0));
if (!old_owner_good && new_owner_good) /* Equivalent to old ServiceCreated signal */
_libnm_glib_get_nm_state (ctx);
else if (old_owner_good && !new_owner_good) /* Equivalent to old ServiceDeleted signal */
ctx->nm_state = LIBNM_NO_NETWORKMANAGER;
}
}
}
else if ( dbus_message_is_signal (message, NM_DBUS_INTERFACE, "DeviceNowActive")
|| dbus_message_is_signal (message, NM_DBUS_INTERFACE, "DeviceNoLongerActive")
|| dbus_message_is_signal (message, NM_DBUS_INTERFACE, "DeviceActivating")
|| dbus_message_is_signal (message, NM_DBUS_INTERFACE, "DevicesChanged"))
{
_libnm_glib_get_nm_state (ctx);
}
else if (dbus_message_is_signal (message, NM_DBUS_INTERFACE, "StateChanged"))
{
NMState state = NM_STATE_UNKNOWN;
dbus_message_get_args (message, &error, DBUS_TYPE_UINT32, &state, DBUS_TYPE_INVALID);
_libnm_glib_update_state (ctx, state);
}
else
handled = FALSE;
if (dbus_error_is_set (&error))
dbus_error_free (&error);
return (handled ? DBUS_HANDLER_RESULT_HANDLED : DBUS_HANDLER_RESULT_NOT_YET_HANDLED);
}
/*
* libnm_glib_dbus_init
*
* Initialize a connection to dbus and set up our callbacks.
*
*/
static DBusConnection *
_libnm_glib_dbus_init (gpointer *user_data, GMainContext *context)
{
DBusConnection *connection = NULL;
DBusError error;
dbus_error_init (&error);
connection = dbus_bus_get_private (DBUS_BUS_SYSTEM, &error);
if (dbus_error_is_set (&error))
{
fprintf (stderr, "%s: error, %s raised:\n %s\n\n", __func__, error.name, error.message);
dbus_error_free (&error);
return (NULL);
}
if (!connection)
return NULL;
if (!dbus_connection_add_filter (connection, _libnm_glib_dbus_filter, user_data, NULL))
return (NULL);
dbus_connection_set_exit_on_disconnect (connection, FALSE);
dbus_connection_setup_with_g_main (connection, context);
dbus_error_init (&error);
dbus_bus_add_match (connection,
"type='signal',"
"interface='" DBUS_INTERFACE_DBUS "',"
"sender='" DBUS_SERVICE_DBUS "'",
&error);
if (dbus_error_is_set (&error))
dbus_error_free (&error);
dbus_error_init (&error);
dbus_bus_add_match (connection,
"type='signal',"
"interface='" NM_DBUS_INTERFACE "',"
"path='" NM_DBUS_PATH "',"
"sender='" NM_DBUS_SERVICE "'",
&error);
if (dbus_error_is_set (&error))
dbus_error_free (&error);
return (connection);
}
/*
* libnm_glib_dbus_watcher
*
* Repeatedly try to re-activate the connection to dbus.
*
*/
static gboolean
_libnm_glib_dbus_watcher (gpointer user_data)
{
libnm_glib_ctx *ctx = (libnm_glib_ctx *)user_data;
g_return_val_if_fail (ctx != NULL, FALSE);
ctx->dbus_watcher = 0;
if (!ctx->dbus_con)
ctx->dbus_con = _libnm_glib_dbus_init ((gpointer)ctx, ctx->g_main_ctx);
if (ctx->dbus_con)
{
/* Get NM's state right away after we reconnect */
_libnm_glib_get_nm_state (ctx);
ctx->dbus_watch_interval = 1000;
}
else
{
/* Wait 3 seconds longer each time we fail to reconnect to dbus,
* with a maximum wait of one minute.
*/
ctx->dbus_watch_interval = MIN(ctx->dbus_watch_interval + 3000, 60000);
/* Reschule ourselves if we _still_ don't have a connection to dbus */
_libnm_glib_schedule_dbus_watcher (ctx);
}
return FALSE;
}
/*
* libnm_glib_schedule_dbus_watcher
*
* Schedule an idle handler in our main loop to repeatedly
* attempt to re-activate the dbus connection until connected.
*
*/
static void
_libnm_glib_schedule_dbus_watcher (libnm_glib_ctx *ctx)
{
g_return_if_fail (ctx != NULL);
if (ctx->dbus_watcher == 0)
{
GSource * source = g_timeout_source_new (ctx->dbus_watch_interval);
g_source_set_callback (source, _libnm_glib_dbus_watcher, (gpointer) ctx, NULL);
ctx->dbus_watcher = g_source_attach (source, ctx->g_main_ctx);
g_source_unref (source);
}
}
/*
* libnm_glib_dbus_worker
*
* Main thread for libnm
*
*/
static gpointer
_libnm_glib_dbus_worker (gpointer user_data)
{
libnm_glib_ctx *ctx = (libnm_glib_ctx *)user_data;
g_return_val_if_fail (ctx != NULL, NULL);
/* If dbus isn't up yet, schedule an idle handler to check for dbus.
* We also need a way to reconnect to dbus if the connection ever goes
* down. Should probably be done by a timeout polling dbus_connection_is_connected()
* or by getting connection status out of libdbus or something.
*/
if (!(ctx->dbus_con = _libnm_glib_dbus_init ((gpointer) ctx, ctx->g_main_ctx)))
_libnm_glib_schedule_dbus_watcher (ctx);
else
_libnm_glib_get_nm_state (ctx);
ctx->thread_inited = TRUE;
g_main_loop_run (ctx->g_main_loop);
ctx->thread_done = TRUE;
return NULL;
}
static void
_libnm_glib_ctx_free (libnm_glib_ctx *ctx)
{
g_return_if_fail (ctx != NULL);
if (ctx->check == 0xDD)
{
fprintf (stderr, "%s: context %p already freed!\n", __func__, ctx);
return;
}
if (ctx->g_main_ctx)
g_main_context_unref (ctx->g_main_ctx);
if (ctx->g_main_loop)
g_main_loop_unref (ctx->g_main_loop);
if (ctx->dbus_con)
{
dbus_connection_close (ctx->dbus_con);
dbus_connection_unref (ctx->dbus_con);
ctx->dbus_con = NULL;
}
if (ctx->callbacks_lock)
g_mutex_free (ctx->callbacks_lock);
g_slist_free_full (ctx->callbacks, g_free);
if (ctx->thread)
g_thread_join (ctx->thread);
memset (ctx, 0, sizeof (libnm_glib_ctx));
memset (&(ctx->check), 0xDD, sizeof (ctx->check));
g_free (ctx);
}
static libnm_glib_ctx *
_libnm_glib_ctx_new (void)
{
libnm_glib_ctx *ctx = g_malloc0 (sizeof (libnm_glib_ctx));
if (!(ctx->g_main_ctx = g_main_context_new ()))
goto error;
if (!(ctx->g_main_loop = g_main_loop_new (ctx->g_main_ctx, FALSE)))
goto error;
if (!(ctx->callbacks_lock = g_mutex_new ()))
goto error;
ctx->dbus_watch_interval = 1000;
return ctx;
error:
_libnm_glib_ctx_free (ctx);
return NULL;
}
libnm_glib_ctx *
libnm_glib_init (void)
{
libnm_glib_ctx *ctx = NULL;
g_type_init ();
if (!g_thread_supported ())
g_thread_init (NULL);
dbus_g_thread_init ();
if (!(ctx = _libnm_glib_ctx_new ()))
return NULL;
ctx->thread = g_thread_create (_libnm_glib_dbus_worker, ctx, TRUE, NULL);
if (!ctx->thread)
goto error;
/* Wait until initialization of the thread */
while (!ctx->thread_inited)
g_usleep (G_USEC_PER_SEC / 20);
return ctx;
error:
_libnm_glib_ctx_free (ctx);
return NULL;
}
void
libnm_glib_shutdown (libnm_glib_ctx *ctx)
{
g_return_if_fail (ctx != NULL);
g_main_loop_quit (ctx->g_main_loop);
while (!ctx->thread_done)
g_usleep (G_USEC_PER_SEC / 20);
_libnm_glib_ctx_free (ctx);
}
libnm_glib_state
libnm_glib_get_network_state (const libnm_glib_ctx *ctx)
{
if (!ctx)
return LIBNM_INVALID_CONTEXT;
return ctx->nm_state;
}
guint
libnm_glib_register_callback (libnm_glib_ctx *ctx,
libnm_glib_callback_func func,
gpointer user_data,
GMainContext *g_main_ctx)
{
libnm_glib_callback *callback = NULL;
g_return_val_if_fail (ctx != NULL, 0);
g_return_val_if_fail (func != NULL, 0);
callback = g_malloc0 (sizeof (libnm_glib_callback));
callback->id = ++ (ctx->callback_id_last);
callback->func = func;
callback->gmain_ctx = g_main_ctx;
callback->libnm_glib_ctx = ctx;
callback->user_data = user_data;
g_mutex_lock (ctx->callbacks_lock);
ctx->callbacks = g_slist_append (ctx->callbacks, callback);
_libnm_glib_schedule_single_callback (ctx, callback);
g_mutex_unlock (ctx->callbacks_lock);
return (callback->id);
}
void
libnm_glib_unregister_callback (libnm_glib_ctx *ctx,
guint id)
{
GSList *elem;
g_return_if_fail (ctx != NULL);
g_return_if_fail (id > 0);
g_mutex_lock (ctx->callbacks_lock);
elem = ctx->callbacks;
while (elem)
{
libnm_glib_callback *callback = (libnm_glib_callback *)(elem->data);
if (callback && (callback->id == id))
{
_libnm_glib_unschedule_single_callback (ctx, callback);
ctx->callbacks = g_slist_remove_link (ctx->callbacks, elem);
break;
}
elem = g_slist_next (elem);
}
g_mutex_unlock (ctx->callbacks_lock);
}