/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
/* NetworkManager -- Network link manager
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Copyright (C) 2007 - 2008 Novell, Inc.
 * Copyright (C) 2007 - 2010 Red Hat, Inc.
 */

#include "config.h"

#include <signal.h>
#include <string.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <libhal.h>
#include <dbus/dbus-glib.h>
#include <dbus/dbus-glib-lowlevel.h>

#if HAVE_LIBUDEV
#define LIBUDEV_I_KNOW_THE_API_IS_SUBJECT_TO_CHANGE
#include <libudev.h>
#endif /* HAVE_LIBUDEV */

#include "nm-glib-compat.h"
#include "nm-hal-manager.h"
#include "nm-marshal.h"
#include "nm-dbus-manager.h"
#include "nm-utils.h"
#include "nm-device-wifi.h"
#include "nm-device-olpc-mesh.h"
#include "nm-device-ethernet.h"
#include "nm-gsm-device.h"
#include "nm-hso-gsm-device.h"
#include "nm-cdma-device.h"

/* Killswitch poll frequency in seconds */
#define RFKILL_POLL_FREQUENCY 6

#define HAL_DBUS_SERVICE "org.freedesktop.Hal"

typedef struct {
	GType device_type;
	char *capability_str;
	char *category;
	gboolean (*is_device_fn) (NMHalManager *self, const char *udi);
	NMDeviceCreatorFn creator_fn;
} DeviceCreator;

static void emit_udi_added (NMHalManager *self, const char *udi, DeviceCreator *creator);

typedef struct {
	LibHalContext *hal_ctx;
	NMDBusManager *dbus_mgr;
	GSList *device_creators;
	gboolean rfkilled;  /* Authoritative rfkill state */

	GSList *deferred_modems;
	guint deferred_modem_id;
	DeviceCreator *modem_creator;

	/* Killswitch handling */
	GSList *killswitch_list;
	guint32 killswitch_poll_id;
	char *kswitch_err;
	gboolean poll_rfkilled;
	guint32 pending_polls;
	GSList *poll_proxies;
	gboolean killswitches_polled;

	gboolean disposed;
} NMHalManagerPrivate;

#define NM_HAL_MANAGER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NM_TYPE_HAL_MANAGER, NMHalManagerPrivate))

G_DEFINE_TYPE (NMHalManager, nm_hal_manager, G_TYPE_OBJECT)

enum {
	UDI_ADDED,
	UDI_REMOVED,
	RFKILL_CHANGED,
	HAL_REAPPEARED,

	LAST_SIGNAL
};

static guint signals[LAST_SIGNAL] = { 0 };


static gboolean poll_killswitches (gpointer user_data);

/* Device creators */

static DeviceCreator *
get_creator (NMHalManager *self, const char *udi)
{
	NMHalManagerPrivate *priv = NM_HAL_MANAGER_GET_PRIVATE (self);
	DeviceCreator *creator;
	GSList *iter;

	for (iter = priv->device_creators; iter; iter = g_slist_next (iter)) {
		creator = (DeviceCreator *) iter->data;

		if (libhal_device_query_capability (priv->hal_ctx, udi, creator->capability_str, NULL) && 
		    creator->is_device_fn (self, udi))
			return creator;
	}

	return NULL;
}

/* end of device creators */

/* Common helpers for built-in device creators */

static char *
hal_get_subsystem (LibHalContext *ctx, const char *udi)
{
	char *subsys;

	subsys = libhal_device_get_property_string (ctx, udi, "info.subsystem", NULL);
	if (!subsys) {
		/* info.bus is deprecated */
		subsys = libhal_device_get_property_string (ctx, udi, "info.bus", NULL);
	}
	return subsys;
}

static char *
nm_get_device_driver_name (LibHalContext *ctx, const char *udi)
{
	char *driver_name = NULL, *drv, *tmp_udi, *tmp2;
	int i = 1;

	g_return_val_if_fail (udi != NULL, NULL);

	/* get the UDI again so we can free it consistently with libhal_free_string */
	tmp_udi = libhal_device_get_property_string (ctx, udi, "info.udi", NULL);

	/* Look at up to 4 ancestors until we get a usable driver */
	do {
		drv = libhal_device_get_property_string (ctx, tmp_udi, "info.linux.driver", NULL);
		if (drv) {
			/* Check if we've gone too far */
			if (   !strcmp (drv, "usb")
			    || !strcmp (drv, "ehci_hcd")
			    || !strcmp (drv, "yenta_cardbus")) {
				/* Some drivers (libertas) are broken and report 'usb' as the
				 * driver name even though the sysfs links are present.  Since
				 * 0.7.2 used to handle that, we should stay compatible.
				 */
				if (strcmp (drv, "usb") != 0) {
					libhal_free_string (drv);
					drv = NULL;
				}
				break;
			}
		} else {
			tmp2 = libhal_device_get_property_string (ctx, tmp_udi, "info.parent", NULL);
			libhal_free_string (tmp_udi);
			tmp_udi = tmp2;

			/* Check if we've gone too far */
			if (tmp_udi && !strcmp (tmp_udi, "/org/freedesktop/Hal/devices/computer"))
				break;
		}
	} while (!drv && (i++ < 4) && tmp_udi);

	libhal_free_string (tmp_udi);

	if (drv) {
		driver_name = g_strdup (drv);
		libhal_free_string (drv);
	}

	return driver_name;
}

static gboolean
get_vid_pid (LibHalContext *ctx,
             const char *udi,
             guint16 *out_vid,
             guint16 *out_pid,
             char **out_parent)
{
	int tmp;
	char *parent;
	DBusError error;
	gboolean success = FALSE;

	g_return_val_if_fail (ctx != NULL, FALSE);
	g_return_val_if_fail (udi != NULL, FALSE);

	parent = libhal_device_get_property_string (ctx, udi, "info.parent", NULL);
	if (!parent)
		return FALSE;

	dbus_error_init (&error);
	tmp = libhal_device_get_property_int (ctx, parent, "usb.vendor_id", &error);
	if (dbus_error_is_set (&error)) {
		dbus_error_free (&error);
		goto out;
	}
	if (tmp < 0 || tmp > G_MAXUINT16)
		goto out;
	if (out_vid)
		*out_vid = (guint16) tmp;

	dbus_error_init (&error);
	tmp = libhal_device_get_property_int (ctx, parent, "usb.product_id", &error);
	if (dbus_error_is_set (&error)) {
		dbus_error_free (&error);
		goto out;
	}
	if (tmp < 0 || tmp > G_MAXUINT16)
		goto out;
	if (out_pid)
		*out_pid = (guint16) tmp;

	if (out_parent)
		*out_parent = g_strdup (parent);
	success = TRUE;

out:
	libhal_free_string (parent);
	return success;
}

/* Returns the parent if the device is a Sony Ericsson 'mbm'-style device */
static gboolean
is_mbm (LibHalContext *ctx, const char *udi, char **out_parent)
{
	guint16 vendor_id = 0, product_id = 0;
	char *parent = NULL;

	if (!get_vid_pid (ctx, udi, &vendor_id, &product_id, &parent))
		return FALSE;

	if (   (vendor_id == 0x0bdb && product_id == 0x1900)    /* SE F3507g */
	    || (vendor_id == 0x0bdb && product_id == 0x1902)    /* SE F3507g */
	    || (vendor_id == 0x0bdb && product_id == 0x1904)    /* SE F3607gw */
	    || (vendor_id == 0x0bdb && product_id == 0x1906)    /* SE F3307 */
	    || (vendor_id == 0x0fce && product_id == 0xd0cf)    /* SE MD300 */
	    || (vendor_id == 0x413c && product_id == 0x8147)    /* Dell 5530 HSDPA */
	    || (vendor_id == 0x0930 && product_id == 0x130b)) { /* Toshiba */
		if (out_parent)
			*out_parent = parent;
		return TRUE;
	}

	g_free (parent);
	return FALSE;
}

/* Wired device creator */

static gboolean
is_wired_device (NMHalManager *self, const char *udi)
{
	NMHalManagerPrivate *priv = NM_HAL_MANAGER_GET_PRIVATE (self);
	char *category;
	gboolean is_wired = FALSE;

	if (libhal_device_property_exists (priv->hal_ctx, udi, "net.linux.ifindex", NULL) &&
		libhal_device_property_exists (priv->hal_ctx, udi, "info.category", NULL)) {

		category = libhal_device_get_property_string (priv->hal_ctx, udi, "info.category", NULL);
		if (category) {
			is_wired = strcmp (category, "net.80203") == 0;
			libhal_free_string (category);
		}
	}

	return is_wired;
}

static GObject *
wired_device_creator (NMHalManager *self,
                      const char *udi,
                      const char *origdev_udi,
                      gboolean managed)
{
	NMHalManagerPrivate *priv = NM_HAL_MANAGER_GET_PRIVATE (self);
	GObject *device = NULL;
	char *iface, *driver;

	iface = libhal_device_get_property_string (priv->hal_ctx, udi, "net.interface", NULL);
	if (!iface) {
		nm_warning ("Couldn't get interface for %s, ignoring.", udi);
		return NULL;
	}

	driver = nm_get_device_driver_name (priv->hal_ctx, udi);

	/* Special handling of Ericsson F3507g 'mbm' devices; ignore the
	 * cdc-ether device that it provides since we don't use it yet.
	 */
	if (!is_mbm (priv->hal_ctx, udi, NULL))
		device = (GObject *) nm_device_ethernet_new (udi, iface, driver, managed);

	libhal_free_string (iface);
	g_free (driver);
	return device;
}

/* Wireless device creator */

static gboolean
is_wireless_device (NMHalManager *self, const char *udi)
{
	NMHalManagerPrivate *priv = NM_HAL_MANAGER_GET_PRIVATE (self);
	char *category;
	gboolean is_wireless = FALSE;

	if (libhal_device_property_exists (priv->hal_ctx, udi, "net.linux.ifindex", NULL) &&
		libhal_device_property_exists (priv->hal_ctx, udi, "info.category", NULL)) {

		category = libhal_device_get_property_string (priv->hal_ctx, udi, "info.category", NULL);
		if (category) {
			is_wireless = strcmp (category, "net.80211") == 0;
			libhal_free_string (category);
		}
	}

	return is_wireless;
}

static GObject *
wireless_device_creator (NMHalManager *self,
                         const char *udi,
                         const char *origdev_udi,
                         gboolean managed)
{
	NMHalManagerPrivate *priv = NM_HAL_MANAGER_GET_PRIVATE (self);
	GObject *device;
	char *iface;
	char *driver;

	iface = libhal_device_get_property_string (priv->hal_ctx, udi, "net.interface", NULL);
	if (!iface) {
		nm_warning ("Couldn't get interface for %s, ignoring.", udi);
		return NULL;
	}

	driver = nm_get_device_driver_name (priv->hal_ctx, udi);
	device = (GObject *) nm_device_wifi_new (udi, iface, driver, managed);

	libhal_free_string (iface);
	g_free (driver);

	return device;
}

/* Modem device creator */

static gboolean
is_modem_device (NMHalManager *self, const char *udi)
{
	NMHalManagerPrivate *priv = NM_HAL_MANAGER_GET_PRIVATE (self);
	gboolean is_modem = FALSE;

	if (libhal_device_property_exists (priv->hal_ctx, udi, "info.category", NULL)) {
		char *category;

		category = libhal_device_get_property_string (priv->hal_ctx, udi, "info.category", NULL);
		if (category) {
			is_modem = strcmp (category, "serial") == 0;
			libhal_free_string (category);
		}
	}

	return is_modem;
}

static char *
hal_get_originating_device (LibHalContext *ctx, const char *udi, const char *prefix)
{
	char *od_key = g_strdup_printf ("%s.originating_device", prefix);
	char *pd_key = g_strdup_printf ("%s.physical_device", prefix);
	char *od;

	od = libhal_device_get_property_string (ctx, udi, od_key, NULL);
	if (!od) {
		/* physical_device is deprecated */
		od = libhal_device_get_property_string (ctx, udi, pd_key, NULL);
	}

	g_free (od_key);
	g_free (pd_key);
	return od;
}

static char *
get_hso_netdev (LibHalContext *ctx, const char *udi)
{
	char *serial_od = NULL, *serial_od_parent = NULL, *netdev = NULL, *subsys;
	char **netdevs = NULL;
	int num, i;

	/* Get the serial interface's originating device UDI, used to find the
	 * originating device's netdev.
	 */
	serial_od = hal_get_originating_device (ctx, udi, "serial");
	if (!serial_od)
		goto out;

	serial_od_parent = libhal_device_get_property_string (ctx, serial_od, "info.parent", NULL);
	if (!serial_od_parent)
		goto out;

	/* Check to ensure we've got the actual "USB Device" */
	subsys = hal_get_subsystem (ctx, serial_od_parent);
	if (!subsys || strcmp (subsys, "usb_device")) {
		libhal_free_string (subsys);
		goto out;
	}
	libhal_free_string (subsys);

	/* Look for the originating device's netdev */
	netdevs = libhal_find_device_by_capability (ctx, "net", &num, NULL);
	for (i = 0; netdevs && !netdev && (i < num); i++) {
		char *net_od = NULL, *net_od_parent = NULL, *tmp;

		net_od = hal_get_originating_device (ctx, netdevs[i], "net");
		if (!net_od)
			goto next;

		net_od_parent = libhal_device_get_property_string (ctx, net_od, "info.parent", NULL);
		if (!net_od_parent)
			goto next;

		/* Check to ensure we've got the actual "USB Device" */
		subsys = hal_get_subsystem (ctx, net_od_parent);
		if (!subsys || strcmp (subsys, "usb_device")) {
			libhal_free_string (subsys);
			goto next;
		}
		libhal_free_string (subsys);

		if (!strcmp (net_od_parent, serial_od_parent)) {
			/* We found it */
			tmp = libhal_device_get_property_string (ctx, netdevs[i], "net.interface", NULL);
			if (tmp)
				netdev = g_strdup (tmp);
			libhal_free_string (tmp);
		}

	next:
		libhal_free_string (net_od_parent);
		libhal_free_string (net_od);
	}

out:
	libhal_free_string_array (netdevs);
	libhal_free_string (serial_od);
	libhal_free_string (serial_od_parent);

	return netdev;
}

#define PROP_GSM      "ID_NM_MODEM_GSM"
#define PROP_CDMA     "ID_NM_MODEM_IS707_A"
#define PROP_EVDO1    "ID_NM_MODEM_IS856"
#define PROP_EVDOA    "ID_NM_MODEM_IS856_A"
#define PROP_ZTE_MAIN "ID_NM_ZTE_PORT_TYPE_MODEM"
#define PROP_ZTE_SET  "ID_NM_ZTE_PORT_TYPE_SET"

#if HAVE_LIBUDEV

typedef struct {
	gboolean gsm;
	gboolean cdma;
	gboolean zte_main;
	gboolean zte_set;
} UdevIterData;

#if UDEV_VERSION >= 129
static const char *
get_udev_property (struct udev_device *device, const char *name)
{
	struct udev_list_entry *entry;

	udev_list_entry_foreach (entry, udev_device_get_properties_list_entry (device)) {
		if (strcmp (udev_list_entry_get_name (entry), name) == 0)
			return udev_list_entry_get_value (entry);
	}

	return NULL;
}
#else
static int udev_device_prop_iter(struct udev_device *udev_device,
                                 const char *key,
                                 const char *value,
                                 void *data)
{
	UdevIterData *types = data;

	if (!strcmp (key, PROP_GSM) && !strcmp (value, "1"))
		types->gsm = TRUE;
	if (!strcmp (key, PROP_CDMA) && !strcmp (value, "1"))
		types->cdma = TRUE;
	if (!strcmp (key, PROP_EVDO1) && !strcmp (value, "1"))
		types->cdma = TRUE;
	if (!strcmp (key, PROP_EVDOA) && !strcmp (value, "1"))
		types->cdma = TRUE;

	if (types->cdma || types->gsm) {
	    if (!strcmp (key, PROP_ZTE_MAIN) && !strcmp (value, "1"))
			types->zte_main = TRUE;
	    if (!strcmp (key, PROP_ZTE_SET) && !strcmp (value, "1"))
			types->zte_set = TRUE;
	}

	/* Return 0 to continue looking */
	return types->gsm && types->cdma;
}
#endif

static gboolean
libudev_get_modem_capabilities (const char *sysfs_path,
                                gboolean *gsm,
                                gboolean *cdma,
                                gboolean *zte_main,
                                gboolean *zte_set)
{
	struct udev *udev;
	struct udev_device *device;

	g_return_val_if_fail (sysfs_path != NULL, FALSE);
	g_return_val_if_fail (gsm != NULL, FALSE);
	g_return_val_if_fail (*gsm == FALSE, FALSE);
	g_return_val_if_fail (cdma != NULL, FALSE);
	g_return_val_if_fail (*cdma == FALSE, FALSE);
	g_return_val_if_fail (zte_main != NULL, FALSE);
	g_return_val_if_fail (*zte_main == FALSE, FALSE);
	g_return_val_if_fail (zte_set != NULL, FALSE);
	g_return_val_if_fail (*zte_set == FALSE, FALSE);

	udev = udev_new ();
	if (!udev)
		return FALSE;

#if UDEV_VERSION >= 129
	device = udev_device_new_from_syspath (udev, sysfs_path);
#else
	/* udev_device_new_from_devpath() requires the sysfs mount point to be
	 * stripped off the path.
	 */
	if (!strncmp (sysfs_path, "/sys", 4))
		sysfs_path += 4;
	device = udev_device_new_from_devpath (udev, sysfs_path);
#endif
	if (!device) {
		udev_unref (udev);
		nm_warning ("couldn't inspect device '%s' with libudev", sysfs_path);
		return FALSE;
	}

#if UDEV_VERSION >= 129
	{
		const char *gsm_val = get_udev_property (device, PROP_GSM);
		const char *cdma_val = get_udev_property (device, PROP_CDMA);
		const char *evdo1_val = get_udev_property (device, PROP_EVDO1);
		const char *evdoa_val = get_udev_property (device, PROP_EVDOA);
		const char *zte_main_val = get_udev_property (device, PROP_ZTE_MAIN);
		const char *zte_set_val = get_udev_property (device, PROP_ZTE_SET);

		if (gsm_val && !strcmp (gsm_val, "1"))
			*gsm = TRUE;
		if (cdma_val && !strcmp (cdma_val, "1"))
			*cdma = TRUE;
		if (evdo1_val && !strcmp (evdo1_val, "1"))
			*cdma = TRUE;
		if (evdoa_val && !strcmp (evdoa_val, "1"))
			*cdma = TRUE;

		if (*gsm || *cdma) {
		    if (zte_main_val && !strcmp (zte_main_val, "1"))
				*zte_main = TRUE;
		    if (zte_set_val && !strcmp (zte_set_val, "1"))
				*zte_set = TRUE;
		}
	}
#else
	{
		UdevIterData iterdata = { FALSE, FALSE };

		udev_device_get_properties (device, udev_device_prop_iter, &iterdata);
		*gsm = iterdata.gsm;
		*cdma = iterdata.cdma;
		if (*gsm || *cdma) {
			*zte_main = iterdata.zte_main;
			*zte_set = iterdata.zte_set;
		}
	}
#endif

	udev_device_unref (device);
	udev_unref (udev);
	return TRUE;
}
#else
static gboolean
udevadm_get_modem_capabilities (const char *sysfs_path,
                                gboolean *gsm,
                                gboolean *cdma,
                                gboolean *zte_main,
                                gboolean *zte_set)
{
	char *udevadm_argv[] = { "/sbin/udevadm", "info", "--query=env", NULL, NULL };
	char *syspath_arg = NULL;
	char *udevadm_stdout = NULL;
	int exitcode;
	GError *error = NULL;
	char **lines = NULL, **iter;
	gboolean success = FALSE, is_zte_main = FALSE, is_zte_set = FALSE;

	g_return_val_if_fail (sysfs_path != NULL, FALSE);
	g_return_val_if_fail (gsm != NULL, FALSE);
	g_return_val_if_fail (*gsm == FALSE, FALSE);
	g_return_val_if_fail (cdma != NULL, FALSE);
	g_return_val_if_fail (*cdma == FALSE, FALSE);
	g_return_val_if_fail (zte_main != NULL, FALSE);
	g_return_val_if_fail (*zte_main == FALSE, FALSE);
	g_return_val_if_fail (zte_set != NULL, FALSE);
	g_return_val_if_fail (*zte_set == FALSE, FALSE);

	udevadm_argv[3] = syspath_arg = g_strdup_printf ("--path=%s", sysfs_path);
	if (g_spawn_sync ("/", udevadm_argv, NULL, 0, NULL, NULL,
			  &udevadm_stdout,
			  NULL,
			  &exitcode,
			  &error) != TRUE) {
		nm_warning ("could not run udevadm to get modem capabilities for '%s': %s",
		            sysfs_path,
		            (error && error->message) ? error->message : "(unknown)");
		g_clear_error (&error);
		goto error;
	}

	if (exitcode != 0) {
		nm_warning ("udevadm error while getting modem capabilities for '%s': %d",
		            sysfs_path, WEXITSTATUS (exitcode));
		goto error;
	}

	lines = g_strsplit_set (udevadm_stdout, "\n\r", -1);
	for (iter = lines; *iter; iter++) {
		if (!strcmp (*iter, PROP_GSM "=1")) {
			*gsm = TRUE;
		} else if (   !strcmp (*iter, PROP_CDMA "=1")
		           || !strcmp (*iter, PROP_EVDO1 "=1")
		           || !strcmp (*iter, PROP_EVDOA "=1")) {
			*cdma = TRUE;
		} else if (!strcmp (*iter, PROP_ZTE_MAIN "=1"))
			is_zte_main = TRUE;
		else if (!strcmp (*iter, PROP_ZTE_SET "=1"))
			is_zte_set = TRUE;
	}

	if (*gsm || *cdma) {
		*zte_main = is_zte_main;
		*zte_set = is_zte_set;
	}

	success = TRUE;

error:
	if (lines)
		g_strfreev (lines);
	g_free (udevadm_stdout);
	g_free (syspath_arg);
	return success;
}
#endif

static void
hal_get_modem_capabilities (LibHalContext *ctx,
                            const char *udi,
                            gboolean *gsm,
                            gboolean *cdma)
{
	char **capabilities, **iter;

	g_return_if_fail (ctx != NULL);
	g_return_if_fail (udi != NULL);
	g_return_if_fail (gsm != NULL);
	g_return_if_fail (*gsm == FALSE);
	g_return_if_fail (cdma != NULL);
	g_return_if_fail (*cdma == FALSE);

	/* Make sure it has the 'modem' capability first */
	if (!libhal_device_query_capability (ctx, udi, "modem", NULL))
		return;

	capabilities = libhal_device_get_property_strlist (ctx, udi, "modem.command_sets", NULL);
	/* 'capabilites' may be NULL */
	for (iter = capabilities; iter && *iter; iter++) {
		if (!strcmp (*iter, "GSM-07.07")) {
			*gsm = TRUE;
			break;
		}
		if (!strcmp (*iter, "IS-707-A")) {
			*cdma = TRUE;
			break;
		}
	}
	if (capabilities)
		g_strfreev (capabilities);
}

typedef struct {
	guint32 refcount;
	char *udi;
	char *origdev_udi;
	gboolean gsm;
	gboolean cdma;
	guint32 usb_interface_number;
	guint32 serial_port;
} DeferredModem;

#if 0
static void
deferred_modem_ref (DeferredModem *modem)
{
	g_return_if_fail (modem != NULL);
	g_return_if_fail (modem->refcount > 0);
	modem->refcount++;
}
#endif

static void
deferred_modem_unref (DeferredModem *modem)
{
	g_return_if_fail (modem != NULL);
	g_return_if_fail (modem->refcount > 0);

	modem->refcount--;
	if (modem->refcount == 0) {
		g_free (modem->udi);
		g_free (modem->origdev_udi);
		memset (modem, 0, sizeof (DeferredModem));
		g_free (modem);
	}
}

static DeferredModem *
deferred_modem_new (const char *udi,
                    const char *origdev_udi,
                    gboolean gsm,
                    gboolean cdma,
                    LibHalContext *ctx)
{
	DeferredModem *modem;
	char *parent;
	dbus_int32_t serial_port = -1, usb_interface = -1;
	DBusError error;

	parent = libhal_device_get_property_string (ctx, udi, "info.parent", NULL);
	if (!parent) {
		g_warning ("couldn't get parent for '%s'", udi);
		return NULL;
	}

	modem = g_new0 (DeferredModem, 1);
	modem->refcount = 1;
	modem->udi = g_strdup (udi);
	modem->origdev_udi = g_strdup (origdev_udi);
	modem->gsm = gsm;
	modem->cdma = cdma;

	dbus_error_init (&error);
	usb_interface = libhal_device_get_property_int (ctx, parent, "usb.interface.number", &error);
	if (dbus_error_is_set (&error)) {
		dbus_error_free (&error);
		usb_interface = -1;
	}

	dbus_error_init (&error);
	serial_port = libhal_device_get_property_int (ctx, udi, "serial.port", &error);
	if (dbus_error_is_set (&error)) {
		dbus_error_free (&error);
		serial_port = -1;
	}

	if (usb_interface == -1 && serial_port == -1) {
		g_warning ("couldn't get usb.interface.number and serial.port for '%s'", udi);
		deferred_modem_unref (modem);
		modem = NULL;
	} else {
		modem->usb_interface_number = usb_interface;
		modem->serial_port = serial_port;
	}

	libhal_free_string (parent);
	return modem;
}

static gint
deferred_modem_sort_func (const DeferredModem *a, const DeferredModem *b)
{
	gint ret;

	/* Sort is: origdev_udi, usb_interface, serial_port */

	ret = strcmp (a->origdev_udi, b->origdev_udi);
	if (ret)
		return ret;

	if (a->usb_interface_number < b->usb_interface_number)
		return -1;
	else if (a->usb_interface_number > b->usb_interface_number)
		return 1;

	if (a->serial_port < b->serial_port)
		return -1;
	else if (a->serial_port > b->serial_port)
		return 1;

	return 0;
}

static DeferredModem *
deferred_modem_find (NMHalManager *self, const char *udi)
{
	NMHalManagerPrivate *priv = NM_HAL_MANAGER_GET_PRIVATE (self);
	GSList *iter;

	for (iter = priv->deferred_modems; iter; iter = g_slist_next (iter)) {
		DeferredModem *candidate = iter->data;

		if (!strcmp (candidate->udi, udi))
			return candidate;
	}
	return NULL;
}

static DeferredModem *
deferred_modem_find_by_origdev (NMHalManager *self, const char *origdev_udi)
{
	NMHalManagerPrivate *priv = NM_HAL_MANAGER_GET_PRIVATE (self);
	GSList *iter;

	for (iter = priv->deferred_modems; iter; iter = g_slist_next (iter)) {
		DeferredModem *candidate = iter->data;

		if (!strcmp (candidate->origdev_udi, origdev_udi))
			return candidate;
	}
	return NULL;
}

static void
deferred_modem_remove (NMHalManager *self, DeferredModem *modem)
{
	NMHalManagerPrivate *priv = NM_HAL_MANAGER_GET_PRIVATE (self);
	GSList *iter, *new = NULL;

	g_return_if_fail (modem != NULL);

	for (iter = priv->deferred_modems; iter; iter = g_slist_next (iter)) {
		if (iter->data != modem)
			new = g_slist_insert_sorted (new, iter->data, (GCompareFunc) deferred_modem_sort_func);
	}
	g_slist_free (priv->deferred_modems);
	priv->deferred_modems = new;
}

static gboolean
deferred_modem_timeout (gpointer data)
{
	NMHalManager *self = data;
	NMHalManagerPrivate *priv = NM_HAL_MANAGER_GET_PRIVATE (self);
	GSList *iter;

	nm_info ("Re-checking deferred serial ports");
	for (iter = priv->deferred_modems; iter; iter = g_slist_next (iter)) {
		DeferredModem *modem = iter->data;

		/* re-emit udi-added for each UDI that was deferred */
		emit_udi_added (self, modem->udi, priv->modem_creator);
	}

	g_slist_foreach (priv->deferred_modems, (GFunc) deferred_modem_unref, NULL);
	g_slist_free (priv->deferred_modems);
	priv->deferred_modems = NULL;
	priv->deferred_modem_id = 0;
	return FALSE;
}

static GObject *
new_modem_device (const char *udi,
                  gboolean gsm,
                  gboolean cdma,
                  const char *ttyname,
                  const char *driver,
                  gboolean managed,
                  LibHalContext *ctx)
{
	GObject *device = NULL;
	char *netdev = NULL;

	g_return_val_if_fail (udi != NULL, NULL);
	g_return_val_if_fail (gsm || cdma, NULL);
	g_return_val_if_fail (ttyname != NULL, NULL);
	g_return_val_if_fail (driver != NULL, NULL);
	g_return_val_if_fail (ctx != NULL, NULL);

	/* Special handling of 'hso' cards (until punted to ModemManager) */
	if (gsm && !strcmp (driver, "hso")) {
		char *hsotype_path;
		char *contents = NULL;
		gboolean success;

		/* We only want the "Control" interface, since that's the only
		 * one that gives us the unsolicited OWANCALL responses.  Ignore
		 * errors since we didn't care about them before.
		 */
		hsotype_path = g_strdup_printf ("/sys/class/tty/%s/hsotype", ttyname);
		success = g_file_get_contents (hsotype_path, &contents, NULL, NULL);
		g_free (hsotype_path);
		if (success && contents) {
			if (   !strstr (contents, "Control")
			    && !strstr (contents, "control")) {
				g_free (contents);
				return NULL;
			}
			g_free (contents);
		}

		netdev = get_hso_netdev (ctx, udi);
		if (!netdev) {
			nm_warning ("couldn't find HSO modem network device.");
			return NULL;
		}
	}

	/* Special handling of Option cards (until punted to ModemManager).  Only
	 * the first USB interface can be used for control and PPP.
	 */
	if (gsm && !strcmp (driver, "option")) {
		char *parent;
		guint32 vendor_id = 0, usb_interface = 0;

		parent = libhal_device_get_property_string (ctx, udi, "info.parent", NULL);
		if (!parent)
			return NULL;

		vendor_id = libhal_device_get_property_int (ctx, parent, "usb.vendor_id", NULL);
		if (vendor_id == 0x0AF0) {
			usb_interface = libhal_device_get_property_int (ctx, parent, "usb.interface.number", NULL);
			if (usb_interface > 0) {
				libhal_free_string (parent);
				return NULL;
			}
		}
		libhal_free_string (parent);
	}

	/* Special handling of Ericsson F3507g 'mbm' devices; already handled by
	 * ModemManager more correctly in HEAD.
	 */
	if (gsm && !strcmp (driver, "cdc_acm")) {
		char *parent = NULL;
		guint32 usb_interface;

		if (is_mbm (ctx, udi, &parent)) {
			usb_interface = libhal_device_get_property_int (ctx, parent, "usb.interface.number", NULL);
			g_free (parent);
			if (usb_interface != 1)
				return NULL;
		}
	}

	if (gsm && netdev) {
		device = (GObject *) nm_hso_gsm_device_new (udi, ttyname, NULL, netdev, driver, managed);
		g_free (netdev);
	} else if (gsm)
		device = (GObject *) nm_gsm_device_new (udi, ttyname, NULL, driver, managed);
	else if (cdma)
		device = (GObject *) nm_cdma_device_new (udi, ttyname, NULL, driver, managed);
	else
		g_assert_not_reached ();

	return device;
}

static GObject *
modem_device_creator (NMHalManager *self,
                      const char *udi,
                      const char *origdev_udi,
                      gboolean managed)
{
	NMHalManagerPrivate *priv = NM_HAL_MANAGER_GET_PRIVATE (self);
	char *serial_device;
	const char *ttyname;
	char *sysfs_path;
	char *driver = NULL;
	GObject *device = NULL;
	gboolean udev_gsm = FALSE;
	gboolean udev_cdma = FALSE;
	gboolean hal_gsm = FALSE;
	gboolean hal_cdma = FALSE;
	gboolean later = FALSE;
	gboolean udev_success = FALSE;
	gboolean zte_main = FALSE;
	gboolean zte_set = FALSE;
	DeferredModem *deferred = NULL;
	guint16 vid = 0;

	serial_device = libhal_device_get_property_string (priv->hal_ctx, udi, "serial.device", NULL);
	/* For serial devices, 'origdev_udi' will be the actual USB or platform device,
	 * not HAL's 'serial.originating_device'.
	 */
	driver = nm_get_device_driver_name (priv->hal_ctx, udi);
	if (!serial_device || !driver)
		goto out;

	ttyname = serial_device + strlen ("/dev/");

	/* If the UDI was deferred from earlier, just make the device with the
	 * cached capabilities.
	 */
	deferred = deferred_modem_find (self, udi);
	if (deferred) {
		udev_gsm = deferred->gsm;
		udev_cdma = deferred->cdma;
		device = new_modem_device (udi, deferred->gsm, deferred->cdma, ttyname, driver, managed, priv->hal_ctx);
		goto out;
	}

	/* Get udev probed capabilities */
	sysfs_path = libhal_device_get_property_string (priv->hal_ctx, udi, "linux.sysfs_path", NULL);
	if (!sysfs_path) {
		nm_warning ("(%s): could not determine sysfs path", ttyname);
		goto out;
	}

#if HAVE_LIBUDEV
	udev_success = libudev_get_modem_capabilities (sysfs_path, &udev_gsm, &udev_cdma, &zte_main, &zte_set);
#else
	udev_success = udevadm_get_modem_capabilities (sysfs_path, &udev_gsm, &udev_cdma, &zte_main, &zte_set);
#endif
	libhal_free_string (sysfs_path);

	/* Get HAL capabilities too */
	hal_get_modem_capabilities (priv->hal_ctx, udi, &hal_gsm, &hal_cdma);

	/* If for some reason running the udev prober failed fall back to HAL */
	if (!udev_success) {
		nm_warning ("(%s): udev probing failed; using only HAL modem capabilities.", ttyname);
		udev_gsm = hal_gsm;
		udev_cdma = hal_cdma;
	}

	/* ZTE modem ports get tagged by udev rules, so if the modem was known to
	 * the rules (in which case ID_NM_ZTE_PORT_TYPE_SET will be set), and the
	 * port isn't a "main" port, we should ignore it.
	 */
	if (get_vid_pid (priv->hal_ctx, udi, &vid, NULL, NULL) && (vid == 0x19d2)) {
		if (zte_set && !zte_main) {
			udev_gsm = FALSE;
			udev_cdma = FALSE;
		}
	}

	/* If it's not known to either udev or HAL as a modem, nothing to do */
	if (!udev_gsm && !udev_cdma && !hal_gsm && !hal_cdma) {
		nm_info ("(%s): ignoring due to lack of mobile broadband capabilties", ttyname);
		goto out;
	}

	nm_info ("(%s): found serial port (udev:%s%s%s  hal:%s%s%s)",
	         ttyname,
	         udev_gsm ? "GSM" : "", udev_gsm && udev_cdma ? "/" : "", udev_cdma ? "CDMA" : "",
	         hal_gsm ? "GSM" : "", hal_gsm && hal_cdma ? "/" : "", hal_cdma ? "CDMA" : "");

	/* If other ports owned by this port's originating device are already
	 * deferred, defer this port as well.
	 */
	later = !!deferred_modem_find_by_origdev (self, origdev_udi);

	/* The logic goes like this:
	 *
	 * a) if both udev and HAL agree on the port command sets, use it
	 * b) if HAL thinks its a modem, but the command set disagrees with udev,
	 *      assume udev probing is correct
	 * c) if HAL thinks it's a modem but udev does not, don't use it
	 * d) if HAL doesn't think it's a modem but udev does, schedule a timer
	 *      to check back later after HAL is done sending ttys.
	 */
	if (udev_gsm == hal_gsm && udev_cdma == hal_cdma) {
		/* Case (a): HAL and udev agree. */
		if (!later)
			device = new_modem_device (udi, udev_gsm, udev_cdma, ttyname, driver, managed, priv->hal_ctx);
	} else if ((hal_gsm || hal_cdma) && (udev_gsm || udev_cdma)) {
		/* Case (b): HAL and udev think it's a modem, but they don't
		 * agree on the command set the modem supports.  Trust udev.
		 */
		if (!later)
			device = new_modem_device (udi, udev_gsm, udev_cdma, ttyname, driver, managed, priv->hal_ctx);
	} else if ((hal_gsm || hal_cdma) && !udev_gsm && !udev_cdma) {
		/* Case (c): HAL thinks it's a modem, but udev doesn't */
		nm_info ("(%s): ignoring due to lack of probed mobile broadband capabilties", ttyname);
		later = FALSE;
	} else {
		/* Case (d): HAL doesn't think its a modem, but udev does */
		later = TRUE;
	}

	if (!device && later) {
		deferred = deferred_modem_new (udi, origdev_udi, udev_gsm, udev_cdma, priv->hal_ctx);
		priv->deferred_modems = g_slist_insert_sorted (priv->deferred_modems,
		                                               deferred,
		                                               (GCompareFunc) deferred_modem_sort_func);

		if (priv->deferred_modem_id)
			g_source_remove (priv->deferred_modem_id);
		priv->deferred_modem_id = g_timeout_add_seconds (8, deferred_modem_timeout, self);

		nm_info ("(%s): deferring until all ports found", ttyname);
	}

out:
	libhal_free_string (serial_device);
	g_free (driver);
	return device;
}

/* OLPC mesh creator */
static gboolean
is_olpc_mesh_device (NMHalManager *self, const char *udi)
{
	NMHalManagerPrivate *priv = NM_HAL_MANAGER_GET_PRIVATE (self);
	gboolean ret = FALSE;

	nm_debug ("Checking if %s is an olpc mesh device", udi);

	if (libhal_device_property_exists (priv->hal_ctx, udi, "info.category", NULL)) {
		char *category;

		category = libhal_device_get_property_string (priv->hal_ctx, udi, "info.category", NULL);
		if (category) {
			ret = strcmp (category, "net.olpcmesh") == 0;
			libhal_free_string (category);
		}
	}

	return ret;
}

static GObject *
olpc_mesh_device_creator (NMHalManager *self,
						  const char *udi,
						  const char *origdev_udi,
						  gboolean managed)
{
	NMHalManagerPrivate *priv = NM_HAL_MANAGER_GET_PRIVATE (self);
	GObject *device;
	char *iface;
	char *driver;

	iface = libhal_device_get_property_string (priv->hal_ctx, udi, "net.interface", NULL);
	if (!iface) {
		nm_warning ("Couldn't get interface for %s, ignoring.", udi);
		return NULL;
	}

	driver = nm_get_device_driver_name (priv->hal_ctx, udi);
	device = (GObject *) nm_device_olpc_mesh_new (udi, iface, driver, managed);

	libhal_free_string (iface);
	g_free (driver);
	return device;
}

static void
register_built_in_creators (NMHalManager *self)
{
	NMHalManagerPrivate *priv = NM_HAL_MANAGER_GET_PRIVATE (self);
	DeviceCreator *creator;

	/* Wired device */
	creator = g_slice_new0 (DeviceCreator);
	creator->device_type = NM_TYPE_DEVICE_ETHERNET;
	creator->capability_str = g_strdup ("net.80203");
	creator->category = g_strdup ("net");
	creator->is_device_fn = is_wired_device;
	creator->creator_fn = wired_device_creator;
	priv->device_creators = g_slist_append (priv->device_creators, creator);

	/* Wireless device */
	creator = g_slice_new0 (DeviceCreator);
	creator->device_type = NM_TYPE_DEVICE_WIFI;
	creator->capability_str = g_strdup ("net.80211");
	creator->category = g_strdup ("net");
	creator->is_device_fn = is_wireless_device;
	creator->creator_fn = wireless_device_creator;
	priv->device_creators = g_slist_append (priv->device_creators, creator);

	/* OLPC mesh device */
	creator = g_slice_new0 (DeviceCreator);
	creator->device_type = NM_TYPE_DEVICE_OLPC_MESH;
	creator->capability_str = g_strdup ("net.olpcmesh");
	creator->category = g_strdup ("net");
	creator->is_device_fn = is_olpc_mesh_device;
	creator->creator_fn = olpc_mesh_device_creator;
	priv->device_creators = g_slist_append (priv->device_creators, creator);

	/* Modem */
	creator = g_slice_new0 (DeviceCreator);
	creator->device_type = NM_TYPE_SERIAL_DEVICE;
	creator->capability_str = g_strdup ("serial");
	creator->category = g_strdup ("serial");
	creator->is_device_fn = is_modem_device;
	creator->creator_fn = modem_device_creator;
	priv->device_creators = g_slist_append (priv->device_creators, creator);
	priv->modem_creator = creator;
}

static void
emit_udi_added (NMHalManager *self, const char *udi, DeviceCreator *creator)
{
	NMHalManagerPrivate *priv = NM_HAL_MANAGER_GET_PRIVATE (self);
	char *od = NULL, *tmp, *parent, *subsys = NULL;

	g_return_if_fail (self != NULL);
	g_return_if_fail (udi != NULL);
	g_return_if_fail (creator != NULL);

	/* For USB serial devices, originating device should really be the "USB Device"
	 * object, which is the grandparent of the tty device.  Only this grandparent
	 * is really common among all ttys of the device; HAL's "serial.originating_device"
	 * points to the "USB Interface" parent of the tty, but this interface only
	 * sometimes has multiple child ttys.  More commonly, the "USB Device"
	 * object provides several "USB Interface" objects, which each provide one tty.
	 * Thus, to ensure that NM only uses the correct tty for the deivce,
	 * we need to filter on the "USB Device" instead of the "USB Interface".
	 */
	parent = libhal_device_get_property_string (priv->hal_ctx, udi, "info.parent", NULL);
	if (parent)
		subsys = hal_get_subsystem (priv->hal_ctx, parent);
	if (subsys && !strcmp (subsys, "usb")) {
		char *usb_intf_udi;

		usb_intf_udi = libhal_device_get_property_string (priv->hal_ctx, udi, "info.parent", NULL);
		if (usb_intf_udi) {
			od = libhal_device_get_property_string (priv->hal_ctx, usb_intf_udi, "info.parent", NULL);

			/* Ensure the grandparent really is the "USB Device" */
			if (od) {
				tmp = hal_get_subsystem (priv->hal_ctx, od);
				if (!tmp || strcmp (tmp, "usb_device")) {
					libhal_free_string (od);
					od = NULL;
				}
				if (tmp)
					libhal_free_string (tmp);
			}
			libhal_free_string (usb_intf_udi);
		}
	}
	libhal_free_string (subsys);
	libhal_free_string (parent);

	/* For non-USB devices, and as a fallback, just use the originating device
	 * of the tty; though this might result in more than one modem being
	 * detected by NM.
	 */
	if (!od)
		od = hal_get_originating_device (priv->hal_ctx, udi, creator->category);

	g_signal_emit (self, signals[UDI_ADDED], 0,
	               udi,
	               od,
	               GSIZE_TO_POINTER (creator->device_type),
	               creator->creator_fn);

	libhal_free_string (od);
}

static void
device_added (LibHalContext *ctx, const char *udi)
{
	NMHalManager *self = NM_HAL_MANAGER (libhal_ctx_get_user_data (ctx));
	DeviceCreator *creator;

	/* If not all the device's properties are set up yet (like net.interface),
	 * the device will actually get added later when HAL signals new device
	 * capabilties.
	 */
	creator = get_creator (self, udi);
	if (creator)
		emit_udi_added (self, udi, creator);
}

static void
device_removed (LibHalContext *ctx, const char *udi)
{
	NMHalManager *self = NM_HAL_MANAGER (libhal_ctx_get_user_data (ctx));
	DeferredModem *modem;

	modem = deferred_modem_find (self, udi);
	if (modem) {
		deferred_modem_remove (self, modem);
		deferred_modem_unref (modem);
	}

	g_signal_emit (self, signals[UDI_REMOVED], 0, udi);
}

static void
device_new_capability (LibHalContext *ctx, const char *udi, const char *capability)
{
	NMHalManager *self = NM_HAL_MANAGER (libhal_ctx_get_user_data (ctx));
	DeviceCreator *creator;

	creator = get_creator (self, udi);
	if (creator)
		emit_udi_added (self, udi, creator);
}

static void
add_initial_devices (NMHalManager *self)
{
	NMHalManagerPrivate *priv = NM_HAL_MANAGER_GET_PRIVATE (self);
	DeviceCreator *creator;
	GSList *iter;
	char **devices;
	int num_devices;
	int i;
	DBusError err;

	for (iter = priv->device_creators; iter; iter = g_slist_next (iter)) {
		creator = (DeviceCreator *) iter->data;

		dbus_error_init (&err);
		devices = libhal_find_device_by_capability (priv->hal_ctx,
		                                            creator->capability_str,
		                                            &num_devices,
		                                            &err);
		if (dbus_error_is_set (&err)) {
			nm_warning ("could not find existing devices: %s", err.message);
			dbus_error_free (&err);
			continue;
		}
		if (!devices)
			continue;

		for (i = 0; i < num_devices; i++) {
			if (creator->is_device_fn (self, devices[i]))
				emit_udi_added (self, devices[i], creator);
		}

		libhal_free_string_array (devices);
	}
}

static void
killswitch_poll_cleanup (NMHalManager *self)
{
	NMHalManagerPrivate *priv = NM_HAL_MANAGER_GET_PRIVATE (self);

	if (priv->poll_proxies) {
		g_slist_foreach (priv->poll_proxies, (GFunc) g_object_unref, NULL);
		g_slist_free (priv->poll_proxies);
		priv->poll_proxies = NULL;
	}

	priv->pending_polls = 0;
	priv->poll_rfkilled = FALSE;
}

static void
killswitch_getpower_done (gpointer user_data)
{
	NMHalManager *self = NM_HAL_MANAGER (user_data);
	NMHalManagerPrivate *priv = NM_HAL_MANAGER_GET_PRIVATE (self);

	priv->pending_polls--;

	if (priv->pending_polls > 0)
		return;

	if (priv->poll_rfkilled != priv->rfkilled || !priv->killswitches_polled) {
		priv->rfkilled = priv->poll_rfkilled;
		nm_info ("Radio killswitches now %s radios",
		         priv->rfkilled ? "block" : "allow");
		g_signal_emit (self, signals[RFKILL_CHANGED], 0, priv->rfkilled);
	}

	killswitch_poll_cleanup (self);

	/* Schedule next poll */
	priv->killswitch_poll_id = g_timeout_add_seconds (RFKILL_POLL_FREQUENCY,
	                                          poll_killswitches,
	                                          self);

	priv->killswitches_polled = TRUE;
}

static void 
killswitch_getpower_reply (DBusGProxy *proxy,
					  DBusGProxyCall *call_id,
					  gpointer user_data)
{
	NMHalManager *self = NM_HAL_MANAGER (user_data);
	NMHalManagerPrivate *priv = NM_HAL_MANAGER_GET_PRIVATE (self);
	int power = 1;
	GError *err = NULL;

	if (dbus_g_proxy_end_call (proxy, call_id, &err,
	                           G_TYPE_INT, &power,
	                           G_TYPE_INVALID)) {
		if (power == 0)
			priv->poll_rfkilled = TRUE;
	} else {
		if (err->message) {
			/* Only print the error if we haven't seen it before */
		    if (!priv->kswitch_err || strcmp (priv->kswitch_err, err->message) != 0) {
				nm_warning ("Error getting killswitch power: %s.", err->message);
				g_free (priv->kswitch_err);
				priv->kswitch_err = g_strdup (err->message);

				/* If there was an error talking to HAL, treat that as rfkilled.
				 * See rh #448889.  On some Dell laptops, dellWirelessCtl
				 * may not be present, but HAL still advertises a killswitch,
				 * and calls to GetPower() will fail.  Thus we cannot assume
				 * that a failure of GetPower() automatically means the wireless
				 * is rfkilled, because in this situation NM would never bring
				 * the radio up.  Only assume failures between NM and HAL should
				 * block the radio, not failures of the HAL killswitch callout
				 * itself.
				 */
				if (strstr (err->message, "Did not receive a reply")) {
					nm_warning ("HAL did not reply to killswitch power request;"
					            " assuming radio is blocked.");
					priv->poll_rfkilled = TRUE;
				}
			}
		}
		g_error_free (err);
	}
}

static void
poll_one_killswitch (gpointer data, gpointer user_data)
{
	NMHalManager *self = NM_HAL_MANAGER (user_data);
	NMHalManagerPrivate *priv = NM_HAL_MANAGER_GET_PRIVATE (self);
	DBusGProxy *proxy;

	proxy = dbus_g_proxy_new_for_name (nm_dbus_manager_get_connection (priv->dbus_mgr),
								"org.freedesktop.Hal",
								(char *) data,
								"org.freedesktop.Hal.Device.KillSwitch");

	dbus_g_proxy_begin_call (proxy, "GetPower",
						killswitch_getpower_reply,
						self,
						killswitch_getpower_done,
						G_TYPE_INVALID);
	priv->pending_polls++;
	priv->poll_proxies = g_slist_prepend (priv->poll_proxies, proxy);
}

static gboolean
poll_killswitches (gpointer user_data)
{
	NMHalManager *self = NM_HAL_MANAGER (user_data);
	NMHalManagerPrivate *priv = NM_HAL_MANAGER_GET_PRIVATE (self);

	killswitch_poll_cleanup (self);

	g_slist_foreach (priv->killswitch_list, poll_one_killswitch, self);
	return FALSE;
}

static void
add_killswitch_device (NMHalManager *self, const char *udi)
{
	NMHalManagerPrivate *priv = NM_HAL_MANAGER_GET_PRIVATE (self);
	char *type;
	GSList *iter;

	type = libhal_device_get_property_string (priv->hal_ctx, udi, "killswitch.type", NULL);
	if (!type)
		return;

	if (strcmp (type, "wlan"))
		goto out;

	/* see if it's already in the list */
	for (iter = priv->killswitch_list; iter; iter = iter->next) {
		const char *list_udi = (const char *) iter->data;
		if (!strcmp (list_udi, udi))
			goto out;
	}

	/* Poll switches if this is the first switch we've found */
	if (!priv->killswitch_list)
		priv->killswitch_poll_id = g_idle_add (poll_killswitches, self);

	priv->killswitch_list = g_slist_append (priv->killswitch_list, g_strdup (udi));
	nm_info ("Found radio killswitch %s", udi);

out:
	libhal_free_string (type);
}

static void
add_killswitch_devices (NMHalManager *self)
{
	NMHalManagerPrivate *priv = NM_HAL_MANAGER_GET_PRIVATE (self);
	char **udis;
	int num_udis;
	int i;
	DBusError	err;

	dbus_error_init (&err);
	udis = libhal_find_device_by_capability (priv->hal_ctx, "killswitch", &num_udis, &err);
	if (!udis)
		return;

	if (dbus_error_is_set (&err)) {
		nm_warning ("Could not find killswitch devices: %s", err.message);
		dbus_error_free (&err);
		return;
	}

	for (i = 0; i < num_udis; i++)
		add_killswitch_device (self, udis[i]);

	libhal_free_string_array (udis);
}

static gboolean
hal_init (NMHalManager *self)
{
	NMHalManagerPrivate *priv = NM_HAL_MANAGER_GET_PRIVATE (self);
	DBusError error;
	DBusGConnection *connection; 

	priv->hal_ctx = libhal_ctx_new ();
	if (!priv->hal_ctx) {
		nm_warning ("Could not get connection to the HAL service.");
		return FALSE;
	}

	connection = nm_dbus_manager_get_connection (priv->dbus_mgr);
	libhal_ctx_set_dbus_connection (priv->hal_ctx,
									dbus_g_connection_get_connection (connection));

	dbus_error_init (&error);
	if (!libhal_ctx_init (priv->hal_ctx, &error)) {
		nm_warning ("libhal_ctx_init() failed: %s\n"
				    "Make sure the hal daemon is running?", 
				    error.message);
		goto error;
	}

	libhal_ctx_set_user_data (priv->hal_ctx, self);
	libhal_ctx_set_device_added (priv->hal_ctx, device_added);
	libhal_ctx_set_device_removed (priv->hal_ctx, device_removed);
	libhal_ctx_set_device_new_capability (priv->hal_ctx, device_new_capability);

	libhal_device_property_watch_all (priv->hal_ctx, &error);
	if (dbus_error_is_set (&error)) {
		nm_error ("libhal_device_property_watch_all(): %s", error.message);
		libhal_ctx_shutdown (priv->hal_ctx, NULL);
		goto error;
	}

	return TRUE;

error:
	if (dbus_error_is_set (&error))
		dbus_error_free (&error);
	if (priv->hal_ctx) {
		libhal_ctx_free (priv->hal_ctx);
		priv->hal_ctx = NULL;
	}
	return FALSE;
}

static void
hal_deinit (NMHalManager *self)
{
	NMHalManagerPrivate *priv = NM_HAL_MANAGER_GET_PRIVATE (self);
	DBusError error;

	if (priv->killswitch_poll_id) {
		g_source_remove (priv->killswitch_poll_id);
		priv->killswitch_poll_id = 0;
	}
	killswitch_poll_cleanup (self);

	if (priv->killswitch_list) {
		g_slist_foreach (priv->killswitch_list, (GFunc) g_free, NULL);
		g_slist_free (priv->killswitch_list);
		priv->killswitch_list = NULL;
	}

	if (!priv->hal_ctx)
		return;

	dbus_error_init (&error);
	libhal_ctx_shutdown (priv->hal_ctx, &error);
	if (dbus_error_is_set (&error)) {
		nm_warning ("libhal shutdown failed - %s", error.message);
		dbus_error_free (&error);
	}

	libhal_ctx_free (priv->hal_ctx);
	priv->hal_ctx = NULL;
}

static void
name_owner_changed (NMDBusManager *dbus_mgr,
					const char *name,
					const char *old,
					const char *new,
					gpointer user_data)
{
	NMHalManager *self = NM_HAL_MANAGER (user_data);
	gboolean old_owner_good = (old && (strlen (old) > 0));
	gboolean new_owner_good = (new && (strlen (new) > 0));

	/* Only care about signals from HAL */
	if (strcmp (name, HAL_DBUS_SERVICE))
		return;

	if (!old_owner_good && new_owner_good) {
		nm_info ("HAL re-appeared");
		/* HAL just appeared */
		if (!hal_init (self))
			nm_warning ("Could not re-connect to HAL!!");
		else
			g_signal_emit (self, signals[HAL_REAPPEARED], 0);
	} else if (old_owner_good && !new_owner_good) {
		/* HAL went away. Bad HAL. */
		nm_info ("HAL disappeared");
		hal_deinit (self);
	}
}

static void
connection_changed (NMDBusManager *dbus_mgr,
					DBusGConnection *connection,
					gpointer user_data)
{
	NMHalManager *self = NM_HAL_MANAGER (user_data);

	if (!connection) {
		hal_deinit (self);
		return;
	}

	if (nm_dbus_manager_name_has_owner (dbus_mgr, HAL_DBUS_SERVICE)) {
		if (!hal_init (self))
			nm_warning ("Could not re-connect to HAL!!");
	}
}

void
nm_hal_manager_query_devices (NMHalManager *self)
{
	NMHalManagerPrivate *priv = NM_HAL_MANAGER_GET_PRIVATE (self);

	/* Find hardware we care about */
	if (priv->hal_ctx) {
		add_killswitch_devices (self);
		add_initial_devices (self);
	}
}

gboolean
nm_hal_manager_udi_exists (NMHalManager *self, const char *udi)
{
	NMHalManagerPrivate *priv = NM_HAL_MANAGER_GET_PRIVATE (self);

	return libhal_device_property_exists (priv->hal_ctx, udi, "info.udi", NULL);
}

NMHalManager *
nm_hal_manager_new (void)
{
	NMHalManager *self;
	NMHalManagerPrivate *priv;

	self = NM_HAL_MANAGER (g_object_new (NM_TYPE_HAL_MANAGER, NULL));

	priv = NM_HAL_MANAGER_GET_PRIVATE (self);
 	if (!nm_dbus_manager_name_has_owner (priv->dbus_mgr, HAL_DBUS_SERVICE)) {
		nm_info ("Waiting for HAL to start...");
		return self;
	}

	if (!hal_init (self)) {
		g_object_unref (self);
		self = NULL;
	}

	return self;
}

static void
nm_hal_manager_init (NMHalManager *self)
{
	NMHalManagerPrivate *priv = NM_HAL_MANAGER_GET_PRIVATE (self);

	priv->rfkilled = FALSE;

	priv->dbus_mgr = nm_dbus_manager_get ();

	register_built_in_creators (self);

	g_signal_connect (priv->dbus_mgr,
	                  "name-owner-changed",
	                  G_CALLBACK (name_owner_changed),
	                  self);
	g_signal_connect (priv->dbus_mgr,
	                  "dbus-connection-changed",
	                  G_CALLBACK (connection_changed),
	                  self);
}

static void
destroy_creator (gpointer data, gpointer user_data)
{
	DeviceCreator *creator = (DeviceCreator *) data;

	g_free (creator->capability_str);
	g_free (creator->category);
	g_slice_free (DeviceCreator, data);
}

static void
dispose (GObject *object)
{
	NMHalManager *self = NM_HAL_MANAGER (object);
	NMHalManagerPrivate *priv = NM_HAL_MANAGER_GET_PRIVATE (self);

	if (priv->disposed) {
		G_OBJECT_CLASS (nm_hal_manager_parent_class)->dispose (object);
		return;
	}
	priv->disposed = TRUE;

	g_object_unref (priv->dbus_mgr);

	g_slist_foreach (priv->device_creators, destroy_creator, NULL);
	g_slist_free (priv->device_creators);

	g_slist_foreach (priv->deferred_modems, (GFunc) deferred_modem_unref, NULL);
	g_slist_free (priv->deferred_modems);

	if (priv->deferred_modem_id)
		g_source_remove (priv->deferred_modem_id);

	hal_deinit (self);

	G_OBJECT_CLASS (nm_hal_manager_parent_class)->dispose (object);	
}

static void
finalize (GObject *object)
{
	NMHalManager *self = NM_HAL_MANAGER (object);
	NMHalManagerPrivate *priv = NM_HAL_MANAGER_GET_PRIVATE (self);

	g_free (priv->kswitch_err);

	G_OBJECT_CLASS (nm_hal_manager_parent_class)->finalize (object);
}

static void
nm_hal_manager_class_init (NMHalManagerClass *klass)
{
	GObjectClass *object_class = G_OBJECT_CLASS (klass);

	g_type_class_add_private (klass, sizeof (NMHalManagerPrivate));

	/* virtual methods */
	object_class->dispose = dispose;
	object_class->finalize = finalize;

	/* Signals */
	signals[UDI_ADDED] =
		g_signal_new ("udi-added",
					  G_OBJECT_CLASS_TYPE (object_class),
					  G_SIGNAL_RUN_FIRST,
					  G_STRUCT_OFFSET (NMHalManagerClass, udi_added),
					  NULL, NULL,
					  _nm_marshal_VOID__STRING_STRING_POINTER_POINTER,
					  G_TYPE_NONE, 4,
					  G_TYPE_STRING, G_TYPE_STRING, G_TYPE_POINTER, G_TYPE_POINTER);

	signals[UDI_REMOVED] =
		g_signal_new ("udi-removed",
					  G_OBJECT_CLASS_TYPE (object_class),
					  G_SIGNAL_RUN_FIRST,
					  G_STRUCT_OFFSET (NMHalManagerClass, udi_removed),
					  NULL, NULL,
					  g_cclosure_marshal_VOID__STRING,
					  G_TYPE_NONE, 1,
					  G_TYPE_STRING);

	signals[RFKILL_CHANGED] =
		g_signal_new ("rfkill-changed",
					  G_OBJECT_CLASS_TYPE (object_class),
					  G_SIGNAL_RUN_FIRST,
					  G_STRUCT_OFFSET (NMHalManagerClass, rfkill_changed),
					  NULL, NULL,
					  g_cclosure_marshal_VOID__BOOLEAN,
					  G_TYPE_NONE, 1,
					  G_TYPE_BOOLEAN);

	signals[HAL_REAPPEARED] =
		g_signal_new ("hal-reappeared",
					  G_OBJECT_CLASS_TYPE (object_class),
					  G_SIGNAL_RUN_FIRST,
					  G_STRUCT_OFFSET (NMHalManagerClass, hal_reappeared),
					  NULL, NULL,
					  g_cclosure_marshal_VOID__VOID,
					  G_TYPE_NONE, 0);
}

