NetworkManager/libnm-glib/libnm_glib.c

600 lines
15 KiB
C
Raw Normal View History

/* -*- 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"
2007-03-02 Tambet Ingo <tambet@ximian.com> * libnm-glib/nm-device-802-11-wireless.c: Cache networks (bssids) list. We get signalled when it changes. * libnm-glib/nm-client.c: Cache NMState and device list, we get signalled when it changes. * libnm-glib/nm-device.c: Cache the device state property. * libnm-glib/nm-access-point.c: Cache the strength property. * src/nm-device-802-11-wireless.c: Fix wireless device scanning scheduler. The new algorithm is to start from SCAN_INTERVAL_MIN (currently defined as 0) and add a SCAN_INTERVAL_STEP (currently 20 seconds) with each successful scan until SCAN_INTERVAL_MAX (currently 120 seconds) is reached. Do not scan while the device is down, activating, or activated (in case of A/B/G cards). Remove some old dead ifdef'ed out code that used to configure wireless devices, it's all done through supplicant now. * src/supplicant-manager/nm-supplicant-interface.c: Fix the reference counting issues with pending calls which caused leaks and crashes when interface was removed (now that the interface actually gets removed). * src/nm-call-store.c: Make a copy of data before running a foreach with user callback on it - The most common usage pattern is to cancel (and thus remove) all pending calls with foreach which would modify the hash table we're iterating over. * src/nm-manager.c: When a device is added, make sure it is "up". When it's removed or disabled due to disabling wireless or networking, bring it down. * include/NetworkManager.h: Add new device state NM_DEVICE_STATE_DOWN. * src/nm-device-802-11-wireless.c: * src/nm-device-802-3-ethernet.c: * src/nm-device.c: - Remove "init" virtual function, all gobjects have a place for that already (constructor). - Replace "start" virtual function with "bring_up", devices can be brought up and down more than just on startup now. - Add "is_up" virtual function. - Implement one way to bring a device down instead of previous 4 different ways, each of witch did something different. * src/NetworkManagerUtils.c (nm_dev_sock_open): This doesn't need an NMDevice, all it needs is the device interface. Get rid of NMData.dev_list (3 members to go). Get rif of NMData in a lot of places. * gnome/libnm_glib/libnm_glib.c: Make it compile again. git-svn-id: http://svn-archive.gnome.org/svn/NetworkManager/trunk@2395 4912f4e0-d625-0410-9fb7-b9a5a253dbdc
2007-03-02 09:30:48 +00:00
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);
2005-03-14 Ray Strode <rstrode@redhat.com> Fourth (probably working) cut at porting to dbus 0.30 api and new hal. This cut adds some new logging macros to make debugging easier. * dispatcher-daemon/NetworkManagerDispatcher.c: * info-daemon/NetworkmanagerInfo.c: * info-daemon/NetworkManagerInfoPassphraseDialog.c: * info-daemon/NetworkManagerInfoVPN.c: * src/NetworkManager.c: * src/NetworkManagerAP.c: * src/NetworkManagerAPList.c: * src/NetworkManagerDHCP.c: * src/NetworkManagerDbus.c: * src/NetworkManagerDevice.c: * src/NetworkManagerPolicy.c: * src/NetworkManagerSystem.c: * src/NetworkManagerUtils.c: * src/NetworkManagerWireless.c: * src/autoip.c: * src/nm-dbus-nm.c: * src/backends/NetworkManagerDebian.c: * src/backends/NetworkManagerGentoo.c: * src/backends/NetworkManagerRedHat.c: * src/backends/NetworkManagerSlackware.c: use new logging macros. * dispatcher-daemon/NetworkManagerDispatcher.c: (nmd_dbus_filter): s/dbus_free/g_free/ * info-daemon/Makefile.am: link in utils library. * info-daemon/NetworkmanagerInfo.c: use new logging macros. (nmi_dbus_get_network): don't assume enumerations are 32-bit. (nmi_dbus_nmi_message_handler): don't free what doesn't belong to us. * libnm_glib/libnm_glib.c: (libnm_glib_get_nm_status): (libnm_glib_init): don't free what doesn't belong to us. (libnm_glib_dbus): strdup result, so it doesn't get lost when message is unref'd. * panel-applet/NMWirelessAppletDbus.c: (nmwa_dbus_update_devices): s/dbus_free/g_free/ * src/NetworkManager.c: (nm_monitor_wired_link_state): request initial status dump of all cards when we start up, instead of relying on /sys/.../carrier. (nm_info_handler), (nm_set_up_log_handlers): log handlers to specify what syslog priorites the logging macros default to. * src/NetworkManagerAPList.c: (nm_ap_list_populate_from_nmi): s/dbus_free_string_array/g_strfreev/ * src/NetworkManagerDbus.c: (nm_dbus_get_network_object): validate d-bus message argument types. Advance message iterator after reading argument, prepend instead of append to GSList. * src/NetworkManagerDevice.c: (nm_device_probe_wired_link_status): remove redundant /sys in /sys path. remove wrong contents == NULL means has carrier assumption. * src/nm-netlink-monitor.c (nm_netlink_monitor_request_status): implement function to ask kernel to dump interface link status over netlink socket. * test/*.c: s/dbus_free/g_free/ * utils/nm-utils.h: (nm_print_backtrace): new macro to print backtrace. (nm_get_timestamp): new macro to get sub-second precise unix timestamp. (nm_info), (nm_debug), (nm_warning), (nm_error): new logging functions. nm_info just prints, nm_debug includes timestamp and function, nm_warning includes function, nm_error includes backtrace and sigtrap. git-svn-id: http://svn-archive.gnome.org/svn/NetworkManager/trunk@497 4912f4e0-d625-0410-9fb7-b9a5a253dbdc
2005-03-15 05:30:15 +00:00
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_foreach (ctx->callbacks, (GFunc)g_free, NULL);
g_slist_free (ctx->callbacks);
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);
2005-01-29 Dan Williams <dcbw@redhat.com> * initscript/RedHat/NetworkManager - Don't spit out sysctl stuff to console * libnm_glib/libnm_glib.c - (libnm_glib_init): call dbus_g_thread_init() * panel-applet/NMWirelessAppletDbus.c - (nmwa_dbus_worker): call dbus_g_thread_init() * src/NetworkManager.c - (main): call dbus_g_thread_init() * src/NetworkManagerAPList.c - (nm_ap_list_print_members): use LOG_ERR instead of LOG_DEBUG so we can actually see what's there in a normal syslog * src/NetworkManagerDevice.c - (nm_device_activate_wireless): print out the "waiting for access point" message only once, then say what access point we actually got after the wait. - (nm_device_need_ap_switch): If a scan is in progress when we're in this function, wait until the scan is done. Scans may change the ESSID of the card, making this function think we need to switch access points - (nm_device_wireless_process_scan_results): for artificial access points don't check against the card's ESSID, but the best_ap's ESSID. This prevents collisions with the scanning code, which may change the card's ESSID and cause the access point to get dropped from the device's AP list. Also increase the keep-around time to 2m from 60s since the max scan interval could be 60s in some cases. * src/NetworkManagerPolicy.c - (nm_policy_activation_finish): Don't add invalid MAC addresses to GConf - (nm_policy_allowed_ap_list_update): When we update, make sure we copy over the new properties and ESSIDs to the device's AP list. Fixes some races between NM and NMI. git-svn-id: http://svn-archive.gnome.org/svn/NetworkManager/trunk@401 4912f4e0-d625-0410-9fb7-b9a5a253dbdc
2005-01-29 23:34:31 +00:00
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);
}