diff options
26 files changed, 4296 insertions, 2813 deletions
diff --git a/Makefile.am b/Makefile.am index e519cedd2f..55ad993927 100644 --- a/Makefile.am +++ b/Makefile.am @@ -3441,11 +3441,8 @@ $(src_devices_bluetooth_libnm_bluetooth_utils_la_OBJECTS): $(libnm_core_lib_h_pu core_plugins += src/devices/bluetooth/libnm-device-plugin-bluetooth.la src_devices_bluetooth_libnm_device_plugin_bluetooth_la_SOURCES = \ - src/devices/bluetooth/nm-bluez-device.c \ - src/devices/bluetooth/nm-bluez-device.h \ src/devices/bluetooth/nm-bluez-manager.c \ - src/devices/bluetooth/nm-bluez5-manager.c \ - src/devices/bluetooth/nm-bluez5-manager.h \ + src/devices/bluetooth/nm-bluez-manager.h \ src/devices/bluetooth/nm-device-bt.c \ src/devices/bluetooth/nm-device-bt.h \ $(NULL) diff --git a/libnm-core/nm-core-internal.h b/libnm-core/nm-core-internal.h index 37523e2564..9f4f19839f 100644 --- a/libnm-core/nm-core-internal.h +++ b/libnm-core/nm-core-internal.h @@ -131,6 +131,10 @@ _nm_setting_secret_flags_valid (NMSettingSecretFlags flags) /*****************************************************************************/ +const char *nm_bluetooth_capability_to_string (NMBluetoothCapabilities capabilities, char *buf, gsize len); + +/*****************************************************************************/ + typedef enum { /*< skip >*/ NM_SETTING_PARSE_FLAGS_NONE = 0, NM_SETTING_PARSE_FLAGS_STRICT = 1LL << 0, diff --git a/libnm-core/nm-utils.c b/libnm-core/nm-utils.c index 5fa0020fa3..ae48b16af4 100644 --- a/libnm-core/nm-utils.c +++ b/libnm-core/nm-utils.c @@ -5812,6 +5812,14 @@ nm_utils_version (void) /*****************************************************************************/ +NM_UTILS_FLAGS2STR_DEFINE (nm_bluetooth_capability_to_string, NMBluetoothCapabilities, + NM_UTILS_FLAGS2STR (NM_BT_CAPABILITY_NONE, "NONE"), + NM_UTILS_FLAGS2STR (NM_BT_CAPABILITY_DUN, "DUN"), + NM_UTILS_FLAGS2STR (NM_BT_CAPABILITY_NAP, "NAP"), +) + +/*****************************************************************************/ + /** * nm_utils_base64secret_decode: * @base64_key: the (possibly invalid) base64 encode key. diff --git a/po/POTFILES.in b/po/POTFILES.in index 90ed9ebdbe..d3246bccfb 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -148,7 +148,7 @@ src/dhcp/nm-dhcp-dhclient-utils.c src/dhcp/nm-dhcp-manager.c src/dns/nm-dns-manager.c src/devices/adsl/nm-device-adsl.c -src/devices/bluetooth/nm-bluez-device.c +src/devices/bluetooth/nm-bluez-manager.c src/devices/bluetooth/nm-device-bt.c src/devices/nm-device-6lowpan.c src/devices/nm-device-bond.c diff --git a/src/devices/bluetooth/meson.build b/src/devices/bluetooth/meson.build index 2def5b74ca..31fbf00674 100644 --- a/src/devices/bluetooth/meson.build +++ b/src/devices/bluetooth/meson.build @@ -1,7 +1,5 @@ sources = files( - 'nm-bluez-device.c', 'nm-bluez-manager.c', - 'nm-bluez5-manager.c', 'nm-bt-error.c', 'nm-device-bt.c', ) diff --git a/src/devices/bluetooth/nm-bluez-device.c b/src/devices/bluetooth/nm-bluez-device.c deleted file mode 100644 index 072582ed87..0000000000 --- a/src/devices/bluetooth/nm-bluez-device.c +++ /dev/null @@ -1,1207 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0+ -/* NetworkManager -- Network link manager - * - * Copyright (C) 2009 - 2012 Red Hat, Inc. - * Copyright (C) 2013 Intel Corporation. - */ - -#include "nm-default.h" - -#include "nm-bluez-device.h" - -#include "nm-core-internal.h" -#include "nm-bt-error.h" -#include "nm-bluez-common.h" -#include "settings/nm-settings.h" -#include "settings/nm-settings-connection.h" -#include "NetworkManagerUtils.h" - -#if WITH_BLUEZ5_DUN -#include "nm-bluez5-dun.h" -#endif - -/*****************************************************************************/ - -#define VARIANT_IS_OF_TYPE_BOOLEAN(v) ((v) != NULL && ( g_variant_is_of_type ((v), G_VARIANT_TYPE_BOOLEAN) )) -#define VARIANT_IS_OF_TYPE_STRING(v) ((v) != NULL && ( g_variant_is_of_type ((v), G_VARIANT_TYPE_STRING) )) -#define VARIANT_IS_OF_TYPE_OBJECT_PATH(v) ((v) != NULL && ( g_variant_is_of_type ((v), G_VARIANT_TYPE_OBJECT_PATH) )) -#define VARIANT_IS_OF_TYPE_STRING_ARRAY(v) ((v) != NULL && ( g_variant_is_of_type ((v), G_VARIANT_TYPE_STRING_ARRAY) )) - -/*****************************************************************************/ - -NM_GOBJECT_PROPERTIES_DEFINE (NMBluezDevice, - PROP_PATH, - PROP_ADDRESS, - PROP_NAME, - PROP_CAPABILITIES, - PROP_USABLE, - PROP_CONNECTED, -); - -enum { - INITIALIZED, - REMOVED, - LAST_SIGNAL -}; - -static guint signals[LAST_SIGNAL] = { 0 }; - -typedef struct { - char *path; - - GDBusConnection *dbus_connection; - - GDBusProxy *proxy; - - GDBusProxy *adapter5; - gboolean adapter_powered; - - gboolean initialized; - gboolean usable; - NMBluetoothCapabilities connection_bt_type; - - guint check_emit_usable_id; - - char *adapter_address; - char *address; - char *name; - guint32 capabilities; - gboolean connected; - gboolean paired; - - char *b4_iface; -#if WITH_BLUEZ5_DUN - NMBluez5DunContext *b5_dun_context; -#endif - - NMSettings *settings; - GSList *connections; - - NMSettingsConnection *pan_connection; - gboolean pan_connection_no_autocreate; -} NMBluezDevicePrivate; - -struct _NMBluezDevice { - GObject parent; - NMBluezDevicePrivate _priv; -}; - -struct _NMBluezDeviceClass { - GObjectClass parent; -}; - -G_DEFINE_TYPE (NMBluezDevice, nm_bluez_device, G_TYPE_OBJECT) - -#define NM_BLUEZ_DEVICE_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMBluezDevice, NM_IS_BLUEZ_DEVICE) - -/*****************************************************************************/ - -#define _NMLOG_PREFIX_NAME "bluez" -#define _NMLOG_DOMAIN LOGD_BT -#define _NMLOG(level, ...) \ - G_STMT_START { \ - if (nm_logging_enabled ((level), (_NMLOG_DOMAIN))) { \ - const char *_path = (self) ? NM_BLUEZ_DEVICE_GET_PRIVATE (self)->path : NULL; \ - \ - _nm_log ((level), \ - (_NMLOG_DOMAIN), \ - 0, \ - NULL, \ - NULL, \ - "%s%s%s" _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \ - NM_PRINT_FMT_QUOTED (_path, _NMLOG_PREFIX_NAME"[", _path, "]: ", _NMLOG_PREFIX_NAME": ") \ - _NM_UTILS_MACRO_REST(__VA_ARGS__)); \ - } \ - } G_STMT_END - -/*****************************************************************************/ - -static void cp_connection_added (NMSettings *settings, - NMSettingsConnection *sett_conn, - NMBluezDevice *self); -static gboolean connection_compatible (NMBluezDevice *self, NMSettingsConnection *sett_conn); - -/*****************************************************************************/ - -const char * -nm_bluez_device_get_path (NMBluezDevice *self) -{ - g_return_val_if_fail (NM_IS_BLUEZ_DEVICE (self), NULL); - - return NM_BLUEZ_DEVICE_GET_PRIVATE (self)->path; -} - -const char * -nm_bluez_device_get_address (NMBluezDevice *self) -{ - g_return_val_if_fail (NM_IS_BLUEZ_DEVICE (self), NULL); - - return NM_BLUEZ_DEVICE_GET_PRIVATE (self)->address; -} - -gboolean -nm_bluez_device_get_initialized (NMBluezDevice *self) -{ - g_return_val_if_fail (NM_IS_BLUEZ_DEVICE (self), FALSE); - - return NM_BLUEZ_DEVICE_GET_PRIVATE (self)->initialized; -} - -gboolean -nm_bluez_device_get_usable (NMBluezDevice *self) -{ - g_return_val_if_fail (NM_IS_BLUEZ_DEVICE (self), FALSE); - - return NM_BLUEZ_DEVICE_GET_PRIVATE (self)->usable; -} - -const char * -nm_bluez_device_get_name (NMBluezDevice *self) -{ - g_return_val_if_fail (NM_IS_BLUEZ_DEVICE (self), NULL); - - return NM_BLUEZ_DEVICE_GET_PRIVATE (self)->name; -} - -guint32 -nm_bluez_device_get_capabilities (NMBluezDevice *self) -{ - g_return_val_if_fail (NM_IS_BLUEZ_DEVICE (self), 0); - - return NM_BLUEZ_DEVICE_GET_PRIVATE (self)->capabilities; -} - -gboolean -nm_bluez_device_get_connected (NMBluezDevice *self) -{ - NMBluezDevicePrivate *priv; - - g_return_val_if_fail (NM_IS_BLUEZ_DEVICE (self), FALSE); - - priv = NM_BLUEZ_DEVICE_GET_PRIVATE (self); - return priv->connected; -} - -static void -pan_connection_check_create (NMBluezDevice *self) -{ - gs_unref_object NMConnection *connection = NULL; - NMSettingsConnection *added; - NMSetting *setting; - gs_free char *id = NULL; - char uuid[37]; - GError *error = NULL; - NMBluezDevicePrivate *priv = NM_BLUEZ_DEVICE_GET_PRIVATE (self); - - g_return_if_fail (priv->capabilities & NM_BT_CAPABILITY_NAP); - g_return_if_fail (priv->connections == NULL); - g_return_if_fail (priv->name); - - if (priv->pan_connection || priv->pan_connection_no_autocreate) { - /* already have a connection or we don't want to create one, nothing to do. */ - return; - } - - /* Only try once to create a connection. If it does not succeed, we do not try again. Also, - * if the connection gets deleted later, do not create another one for this device. */ - priv->pan_connection_no_autocreate = TRUE; - - /* create a new connection */ - - connection = nm_simple_connection_new (); - - /* Setting: Connection */ - nm_utils_uuid_generate_buf (uuid); - id = g_strdup_printf (_("%s Network"), priv->name); - setting = nm_setting_connection_new (); - g_object_set (setting, - NM_SETTING_CONNECTION_ID, id, - NM_SETTING_CONNECTION_UUID, uuid, - NM_SETTING_CONNECTION_AUTOCONNECT, FALSE, - NM_SETTING_CONNECTION_TYPE, NM_SETTING_BLUETOOTH_SETTING_NAME, - NULL); - nm_connection_add_setting (connection, setting); - - /* Setting: Bluetooth */ - setting = nm_setting_bluetooth_new (); - g_object_set (G_OBJECT (setting), - NM_SETTING_BLUETOOTH_BDADDR, priv->address, - NM_SETTING_BLUETOOTH_TYPE, NM_SETTING_BLUETOOTH_TYPE_PANU, - NULL); - nm_connection_add_setting (connection, setting); - - if (!nm_connection_normalize (connection, NULL, NULL, &error)) { - _LOGE ("couldn't generate a connection for NAP device: %s", - error->message); - g_error_free (error); - g_return_if_reached (); - } - - /* Adding a new connection raises a signal which eventually calls check_emit_usable (again) - * which then already finds the suitable connection in priv->connections. This is confusing, - * so block the signal. check_emit_usable will succeed after this function call returns. */ - g_signal_handlers_block_by_func (priv->settings, cp_connection_added, self); - nm_settings_add_connection (priv->settings, - connection, - NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_ONLY, - NM_SETTINGS_CONNECTION_ADD_REASON_NONE, - NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED, - &added, - &error); - g_signal_handlers_unblock_by_func (priv->settings, cp_connection_added, self); - - if (added) { - nm_assert (!g_slist_find (priv->connections, added)); - nm_assert (connection_compatible (self, added)); - priv->connections = g_slist_prepend (priv->connections, g_object_ref (added)); - priv->pan_connection = added; - _LOGD ("added new Bluetooth connection for NAP device: '%s' (%s)", - id, uuid); - } else { - _LOGW ("couldn't add new Bluetooth connection for NAP device: '%s' (%s): %s", - id, uuid, error->message); - g_clear_error (&error); - } -} - -static gboolean -check_emit_usable (NMBluezDevice *self) -{ - NMBluezDevicePrivate *priv = NM_BLUEZ_DEVICE_GET_PRIVATE (self); - gboolean new_usable; - - /* only expect the supported capabilities set. */ - nm_assert ((priv->capabilities & ~(NM_BT_CAPABILITY_NAP | NM_BT_CAPABILITY_DUN)) == NM_BT_CAPABILITY_NONE ); - - new_usable = ( priv->initialized - && priv->capabilities - && priv->name - && priv->paired - && priv->adapter5 - && priv->adapter_powered - && priv->dbus_connection - && priv->address - && priv->adapter_address); - - if (!new_usable) - goto END; - - if (priv->connections) - goto END; - - if (!(priv->capabilities & NM_BT_CAPABILITY_NAP)) { - /* non NAP devices are only usable, if they already have a connection. */ - new_usable = FALSE; - goto END; - } - - pan_connection_check_create (self); - new_usable = !!priv->pan_connection; - -END: - if (new_usable != priv->usable) { - priv->usable = new_usable; - _notify (self, PROP_USABLE); - } - - return G_SOURCE_REMOVE; -} - -static void -check_emit_usable_schedule (NMBluezDevice *self) -{ - NMBluezDevicePrivate *priv = NM_BLUEZ_DEVICE_GET_PRIVATE (self); - - if (priv->check_emit_usable_id == 0) - priv->check_emit_usable_id = g_idle_add ((GSourceFunc) check_emit_usable, self); -} - -/*****************************************************************************/ - -static gboolean -connection_compatible (NMBluezDevice *self, NMSettingsConnection *sett_conn) -{ - NMBluezDevicePrivate *priv = NM_BLUEZ_DEVICE_GET_PRIVATE (self); - NMConnection *connection = nm_settings_connection_get_connection (sett_conn); - NMSettingBluetooth *s_bt; - const char *bt_type; - const char *bdaddr; - - if (!nm_connection_is_type (connection, NM_SETTING_BLUETOOTH_SETTING_NAME)) - return FALSE; - - s_bt = nm_connection_get_setting_bluetooth (connection); - if (!s_bt) - return FALSE; - - if (!priv->address) - return FALSE; - - bdaddr = nm_setting_bluetooth_get_bdaddr (s_bt); - if (!bdaddr) - return FALSE; - if (!nm_utils_hwaddr_matches (bdaddr, -1, priv->address, -1)) - return FALSE; - - bt_type = nm_setting_bluetooth_get_connection_type (s_bt); - - if (nm_streq (bt_type, NM_SETTING_BLUETOOTH_TYPE_NAP)) - return FALSE; - - if ( g_str_equal (bt_type, NM_SETTING_BLUETOOTH_TYPE_DUN) - && !(priv->capabilities & NM_BT_CAPABILITY_DUN)) - return FALSE; - - if ( g_str_equal (bt_type, NM_SETTING_BLUETOOTH_TYPE_PANU) - && !(priv->capabilities & NM_BT_CAPABILITY_NAP)) - return FALSE; - - return TRUE; -} - -static gboolean -_internal_track_connection (NMBluezDevice *self, - NMSettingsConnection *sett_conn, - gboolean tracked) -{ - NMBluezDevicePrivate *priv = NM_BLUEZ_DEVICE_GET_PRIVATE (self); - gboolean was_tracked; - - was_tracked = !!g_slist_find (priv->connections, sett_conn); - if (was_tracked == !!tracked) - return FALSE; - - if (tracked) - priv->connections = g_slist_prepend (priv->connections, g_object_ref (sett_conn)); - else { - priv->connections = g_slist_remove (priv->connections, sett_conn); - if (priv->pan_connection == sett_conn) - priv->pan_connection = NULL; - g_object_unref (sett_conn); - } - - return TRUE; -} - -static void -cp_connection_added (NMSettings *settings, - NMSettingsConnection *sett_conn, - NMBluezDevice *self) -{ - if (connection_compatible (self, sett_conn)) { - if (_internal_track_connection (self, sett_conn, TRUE)) - check_emit_usable (self); - } -} - -static void -cp_connection_removed (NMSettings *settings, - NMSettingsConnection *sett_conn, - NMBluezDevice *self) -{ - if (_internal_track_connection (self, sett_conn, FALSE)) - check_emit_usable (self); -} - -static void -cp_connection_updated (NMSettings *settings, - NMSettingsConnection *sett_conn, - guint update_reason_u, - NMBluezDevice *self) -{ - if (_internal_track_connection (self, sett_conn, - connection_compatible (self, sett_conn))) - check_emit_usable_schedule (self); -} - -static void -load_connections (NMBluezDevice *self) -{ - NMBluezDevicePrivate *priv = NM_BLUEZ_DEVICE_GET_PRIVATE (self); - NMSettingsConnection *const*connections; - guint i; - gboolean changed = FALSE; - - connections = nm_settings_get_connections (priv->settings, NULL); - for (i = 0; connections[i]; i++) { - if (connection_compatible (self, connections[i])) - changed |= _internal_track_connection (self, connections[i], TRUE); - } - if (changed) - check_emit_usable (self); -} - -/*****************************************************************************/ - -static void -bluez_disconnect_cb (GDBusConnection *dbus_connection, - GAsyncResult *res, - gpointer user_data) -{ - gs_unref_object NMBluezDevice *self = user_data; - gs_free_error GError *error = NULL; - gs_unref_variant GVariant *variant = NULL; - - variant = g_dbus_connection_call_finish (dbus_connection, res, &error); - if (!variant) { - if (!strstr (error->message, "org.bluez.Error.NotConnected")) - _LOGW ("failed to disconnect: %s", error->message); - } -} - -void -nm_bluez_device_disconnect (NMBluezDevice *self) -{ - NMBluezDevicePrivate *priv = NM_BLUEZ_DEVICE_GET_PRIVATE (self); - GVariant *args = NULL; - const char *dbus_iface = NULL; - - g_return_if_fail (priv->dbus_connection); - - /* FIXME: if we are in the process of connecting and cancel the - * connection attempt, we must complete the pending connect request. - * However, we must also ensure that we don't leave a connected device. */ - if (priv->connection_bt_type == NM_BT_CAPABILITY_DUN) { -#if WITH_BLUEZ5_DUN - nm_bluez5_dun_cleanup (priv->b5_dun_context); -#endif - priv->connected = FALSE; - goto out; - } else if (priv->connection_bt_type == NM_BT_CAPABILITY_NAP) { - dbus_iface = NM_BLUEZ5_NETWORK_INTERFACE; - } else - nm_assert_not_reached (); - - g_dbus_connection_call (priv->dbus_connection, - NM_BLUEZ_SERVICE, - priv->path, - dbus_iface, - "Disconnect", - args ?: g_variant_new("()"), - NULL, - G_DBUS_CALL_FLAGS_NONE, - 10000, - NULL, - (GAsyncReadyCallback) bluez_disconnect_cb, - g_object_ref (self)); - -out: - g_clear_pointer (&priv->b4_iface, g_free); - priv->connection_bt_type = NM_BT_CAPABILITY_NONE; -} - -static void -_connect_complete (NMBluezDevice *self, - const char *device, - NMBluezDeviceConnectCallback callback, - gpointer callback_user_data, - GError *error) -{ - NMBluezDevicePrivate *priv = NM_BLUEZ_DEVICE_GET_PRIVATE (self); - - nm_assert ((device || error) && !(device && error)); - - if (device) { - priv->connected = TRUE; - _notify (self, PROP_CONNECTED); - } - - if (callback) - callback (self, device, error, callback_user_data); -} - -static void -_connect_cb (GObject *source_object, - GAsyncResult *res, - gpointer user_data) -{ - gs_unref_object NMBluezDevice *self = NULL; - NMBluezDevicePrivate *priv; - NMBluezDeviceConnectCallback callback; - gpointer callback_user_data; - gs_free_error GError *error = NULL; - char *device = NULL; - gs_unref_variant GVariant *variant = NULL; - - nm_utils_user_data_unpack (user_data, &self, &callback, &callback_user_data); - - priv = NM_BLUEZ_DEVICE_GET_PRIVATE (self); - - variant = _nm_dbus_connection_call_finish (G_DBUS_CONNECTION (source_object), res, G_VARIANT_TYPE ("(s)"), &error); - if (variant) { - g_variant_get (variant, "(s)", &device); - priv->b4_iface = device; - } - - _connect_complete (self, device, callback, callback_user_data, error); -} - -#if WITH_BLUEZ5_DUN -static void -_connect_cb_bluez5_dun (NMBluez5DunContext *context, - const char *device, - GError *error, - gpointer user_data) -{ - gs_unref_object NMBluezDevice *self = NULL; - gs_unref_object GCancellable *cancellable = NULL; - NMBluezDeviceConnectCallback callback; - gpointer callback_user_data; - gs_free_error GError *cancelled_error = NULL; - - nm_utils_user_data_unpack (user_data, &self, &cancellable, &callback, &callback_user_data); - - /* FIXME(shutdown): the async operation nm_bluez5_dun_connect() should be cancellable. - * Fake it here. */ - if (g_cancellable_set_error_if_cancelled (cancellable, &cancelled_error)) - error = cancelled_error; - - _connect_complete (self, device, callback, callback_user_data, error); -} -#else /* WITH_BLUEZ5_DUN */ -static void -_connect_cb_bluez5_dun_idle_no_b5 (gpointer user_data, - GCancellable *cancellable) -{ - gs_unref_object NMBluezDevice *self = NULL; - NMBluezDeviceConnectCallback callback; - gpointer callback_user_data; - gs_free_error GError *error = NULL; - - nm_utils_user_data_unpack (user_data, &self, &callback, &callback_user_data); - - if (!g_cancellable_set_error_if_cancelled (cancellable, &error)) { - g_set_error (&error, - NM_BT_ERROR, - NM_BT_ERROR_DUN_CONNECT_FAILED, - "NetworkManager built without support for Bluez 5"); - } - callback (self, NULL, error, callback_user_data); -} -#endif /* WITH_BLUEZ5_DUN */ - -void -nm_bluez_device_connect_async (NMBluezDevice *self, - NMBluetoothCapabilities connection_bt_type, - GCancellable *cancellable, - NMBluezDeviceConnectCallback callback, - gpointer callback_user_data) -{ - NMBluezDevicePrivate *priv = NM_BLUEZ_DEVICE_GET_PRIVATE (self); - const char *dbus_iface = NULL; - const char *connect_type = NULL; - - g_return_if_fail (priv->capabilities & connection_bt_type & (NM_BT_CAPABILITY_DUN | NM_BT_CAPABILITY_NAP)); - - priv->connection_bt_type = connection_bt_type; - - if (connection_bt_type == NM_BT_CAPABILITY_NAP) { - connect_type = BLUETOOTH_CONNECT_NAP; - dbus_iface = NM_BLUEZ5_NETWORK_INTERFACE; - } else if (connection_bt_type == NM_BT_CAPABILITY_DUN) { - connect_type = BLUETOOTH_CONNECT_DUN; -#if WITH_BLUEZ5_DUN - if (priv->b5_dun_context == NULL) - priv->b5_dun_context = nm_bluez5_dun_new (priv->adapter_address, priv->address); - nm_bluez5_dun_connect (priv->b5_dun_context, - _connect_cb_bluez5_dun, - nm_utils_user_data_pack (g_object_ref (self), - nm_g_object_ref (cancellable), - callback, - callback_user_data)); -#else - if (callback) { - nm_utils_invoke_on_idle (_connect_cb_bluez5_dun_idle_no_b5, - nm_utils_user_data_pack (g_object_ref (self), - callback, - callback_user_data), - cancellable); - } -#endif - return; - } else - g_return_if_reached (); - - /* FIXME: we need to remember that a connect is in progress. - * So, if the request gets cancelled, that we disconnect the - * connection that was established in the meantime. */ - g_dbus_connection_call (priv->dbus_connection, - NM_BLUEZ_SERVICE, - priv->path, - dbus_iface, - "Connect", - g_variant_new ("(s)", connect_type), - NULL, - G_DBUS_CALL_FLAGS_NONE, - 20000, - cancellable, - _connect_cb, - nm_utils_user_data_pack (g_object_ref (self), - callback, - callback_user_data)); -} - -/*****************************************************************************/ - -static void -set_adapter_address (NMBluezDevice *self, const char *address) -{ - NMBluezDevicePrivate *priv = NM_BLUEZ_DEVICE_GET_PRIVATE (self); - - g_return_if_fail (address); - - if (priv->adapter_address) - g_free (priv->adapter_address); - priv->adapter_address = g_strdup (address); -} - -static guint32 -convert_uuids_to_capabilities (const char **strings) -{ - const char **iter; - guint32 capabilities = 0; - - for (iter = strings; iter && *iter; iter++) { - char **parts; - - parts = g_strsplit (*iter, "-", -1); - if (parts && parts[0]) { - switch (g_ascii_strtoull (parts[0], NULL, 16)) { - case 0x1103: - capabilities |= NM_BT_CAPABILITY_DUN; - break; - case 0x1116: - capabilities |= NM_BT_CAPABILITY_NAP; - break; - default: - break; - } - } - g_strfreev (parts); - } - - return capabilities; -} - -static void -_set_property_capabilities (NMBluezDevice *self, const char **uuids) -{ - guint32 uint_val; - NMBluezDevicePrivate *priv = NM_BLUEZ_DEVICE_GET_PRIVATE (self); - - uint_val = convert_uuids_to_capabilities (uuids); - if (priv->capabilities != uint_val) { - if (priv->capabilities) { - /* changing (relevant) capabilities is not supported and ignored -- except setting initially */ - _LOGW ("ignore change of capabilities for Bluetooth device from %u to %u", - priv->capabilities, uint_val); - return; - } - _LOGD ("set capabilities for Bluetooth device: %s%s%s", - uint_val & NM_BT_CAPABILITY_NAP ? "NAP" : "", - ((uint_val & NM_BT_CAPABILITY_DUN) && (uint_val &NM_BT_CAPABILITY_NAP)) ? " | " : "", - uint_val & NM_BT_CAPABILITY_DUN ? "DUN" : ""); - priv->capabilities = uint_val; - _notify (self, PROP_CAPABILITIES); - } -} - -/** - * priv->address can only be set one to a certain (non NULL) value. Every later attempt - * to reset it to another value will be ignored and a warning will be logged. - **/ -static void -_set_property_address (NMBluezDevice *self, const char *addr) -{ - NMBluezDevicePrivate *priv = NM_BLUEZ_DEVICE_GET_PRIVATE (self); - - if (g_strcmp0 (priv->address, addr) == 0) - return; - - if (!addr) { - _LOGW ("cannot reset address from '%s' to NULL", priv->address); - return; - } - - if (priv->address != NULL) { - _LOGW ("cannot reset address from '%s' to '%s'", priv->address, addr); - return; - } - - if (!nm_utils_hwaddr_valid (addr, ETH_ALEN)) { - _LOGW ("cannot set address to '%s' (invalid value)", addr); - return; - } - - priv->address = g_strdup (addr); - _notify (self, PROP_ADDRESS); -} - -static void -_take_variant_property_address (NMBluezDevice *self, GVariant *v) -{ - _set_property_address (self, VARIANT_IS_OF_TYPE_STRING (v) ? g_variant_get_string (v, NULL) : NULL); - if (v) - g_variant_unref (v); -} - -static void -_take_variant_property_name (NMBluezDevice *self, GVariant *v) -{ - NMBluezDevicePrivate *priv = NM_BLUEZ_DEVICE_GET_PRIVATE (self); - const char *str; - - if (VARIANT_IS_OF_TYPE_STRING (v)) { - str = g_variant_get_string (v, NULL); - if (g_strcmp0 (priv->name, str)) { - g_free (priv->name); - priv->name = g_strdup (str); - _notify (self, PROP_NAME); - } - } - if (v) - g_variant_unref (v); -} - -static void -_take_variant_property_uuids (NMBluezDevice *self, GVariant *v) -{ - if (VARIANT_IS_OF_TYPE_STRING_ARRAY (v)) { - const char **uuids = g_variant_get_strv (v, NULL); - - _set_property_capabilities (self, uuids); - g_free (uuids); - } - if (v) - g_variant_unref (v); -} - -static void -_take_variant_property_connected (NMBluezDevice *self, GVariant *v) -{ - NMBluezDevicePrivate *priv = NM_BLUEZ_DEVICE_GET_PRIVATE (self); - - if (VARIANT_IS_OF_TYPE_BOOLEAN (v)) { - gboolean connected = g_variant_get_boolean (v); - - if (priv->connected != connected) { - priv->connected = connected; - _notify (self, PROP_CONNECTED); - } - } - if (v) - g_variant_unref (v); -} - -static void -_take_variant_property_paired (NMBluezDevice *self, GVariant *v) -{ - NMBluezDevicePrivate *priv = NM_BLUEZ_DEVICE_GET_PRIVATE (self); - - if (VARIANT_IS_OF_TYPE_BOOLEAN (v)) - priv->paired = g_variant_get_boolean (v); - - if (v) - g_variant_unref (v); -} - -static void -adapter5_on_properties_changed (GDBusProxy *proxy, - GVariant *changed_properties, - GStrv invalidated_properties, - gpointer user_data) -{ - NMBluezDevice *self = NM_BLUEZ_DEVICE (user_data); - NMBluezDevicePrivate *priv = NM_BLUEZ_DEVICE_GET_PRIVATE (self); - GVariantIter i; - const char *property; - GVariant *v; - - g_variant_iter_init (&i, changed_properties); - while (g_variant_iter_next (&i, "{&sv}", &property, &v)) { - if (!strcmp (property, "Powered") && VARIANT_IS_OF_TYPE_BOOLEAN (v)) { - gboolean powered = g_variant_get_boolean (v); - if (priv->adapter_powered != powered) - priv->adapter_powered = powered; - } - g_variant_unref (v); - } - - check_emit_usable (self); -} - -static void -adapter5_on_acquired (GObject *object, GAsyncResult *res, NMBluezDevice *self) -{ - NMBluezDevicePrivate *priv = NM_BLUEZ_DEVICE_GET_PRIVATE (self); - GError *error = NULL; - GVariant *v; - - priv->adapter5 = g_dbus_proxy_new_for_bus_finish (res, &error); - if (!priv->adapter5) { - _LOGW ("failed to acquire adapter proxy: %s", error->message); - g_clear_error (&error); - g_signal_emit (self, signals[INITIALIZED], 0, FALSE); - } else { - g_signal_connect (priv->adapter5, "g-properties-changed", - G_CALLBACK (adapter5_on_properties_changed), self); - - /* Check adapter's powered state */ - v = g_dbus_proxy_get_cached_property (priv->adapter5, "Powered"); - priv->adapter_powered = VARIANT_IS_OF_TYPE_BOOLEAN (v) ? g_variant_get_boolean (v) : FALSE; - if (v) - g_variant_unref (v); - - v = g_dbus_proxy_get_cached_property (priv->adapter5, "Address"); - if (VARIANT_IS_OF_TYPE_STRING (v)) - set_adapter_address (self, g_variant_get_string (v, NULL)); - - priv->initialized = TRUE; - g_signal_emit (self, signals[INITIALIZED], 0, TRUE); - - check_emit_usable (self); - } - - g_object_unref (self); -} - -static void -_take_one_variant_property (NMBluezDevice *self, const char *property, GVariant *v) -{ - if (v) { - if (!g_strcmp0 (property, "Address")) - _take_variant_property_address (self, v); - else if (!g_strcmp0 (property, "Connected")) - _take_variant_property_connected (self, v); - else if (!g_strcmp0 (property, "Paired")) - _take_variant_property_paired (self, v); - else if (!g_strcmp0 (property, "Name")) - _take_variant_property_name (self, v); - else if (!g_strcmp0 (property, "UUIDs")) - _take_variant_property_uuids (self, v); - else - g_variant_unref (v); - } -} - -static void -_set_properties (NMBluezDevice *self, GVariant *properties) -{ - GVariantIter i; - const char *property; - GVariant *v; - - g_object_freeze_notify (G_OBJECT (self)); - g_variant_iter_init (&i, properties); - while (g_variant_iter_next (&i, "{&sv}", &property, &v)) - _take_one_variant_property (self, property, v); - g_object_thaw_notify (G_OBJECT (self)); -} - -static void -properties_changed (GDBusProxy *proxy, - GVariant *changed_properties, - GStrv invalidated_properties, - gpointer user_data) -{ - NMBluezDevice *self = NM_BLUEZ_DEVICE (user_data); - - _set_properties (self, changed_properties); - check_emit_usable (self); -} - -static void -query_properties (NMBluezDevice *self) -{ - NMBluezDevicePrivate *priv = NM_BLUEZ_DEVICE_GET_PRIVATE (self); - GVariant *v; - - g_object_freeze_notify (G_OBJECT (self)); - _take_variant_property_address (self, g_dbus_proxy_get_cached_property (priv->proxy, "Address")); - _take_variant_property_connected (self, g_dbus_proxy_get_cached_property (priv->proxy, "Connected")); - _take_variant_property_paired (self, g_dbus_proxy_get_cached_property (priv->proxy, "Paired")); - _take_variant_property_name (self, g_dbus_proxy_get_cached_property (priv->proxy, "Name")); - _take_variant_property_uuids (self, g_dbus_proxy_get_cached_property (priv->proxy, "UUIDs")); - g_object_thaw_notify (G_OBJECT (self)); - - v = g_dbus_proxy_get_cached_property (priv->proxy, "Adapter"); - if (VARIANT_IS_OF_TYPE_OBJECT_PATH (v)) { - g_dbus_proxy_new_for_bus (G_BUS_TYPE_SYSTEM, - G_DBUS_PROXY_FLAGS_NONE, - NULL, - NM_BLUEZ_SERVICE, - g_variant_get_string (v, NULL), - NM_BLUEZ5_ADAPTER_INTERFACE, - NULL, - (GAsyncReadyCallback) adapter5_on_acquired, - g_object_ref (self)); - g_variant_unref (v); - } else { - /* If the Adapter property is unset at this point, we won't try to acquire the adapter later on - * and the device stays unusable. This should not happen, but if it does, log a debug message. */ - _LOGD ("device has no adapter property and cannot be used"); - } - - /* Check if any connections match this device */ - load_connections (self); -} - -static void -on_proxy_acquired (GObject *object, GAsyncResult *res, NMBluezDevice *self) -{ - NMBluezDevicePrivate *priv = NM_BLUEZ_DEVICE_GET_PRIVATE (self); - GError *error = NULL; - - priv->proxy = g_dbus_proxy_new_for_bus_finish (res, &error); - - if (!priv->proxy) { - _LOGW ("failed to acquire device proxy: %s", error->message); - g_clear_error (&error); - g_signal_emit (self, signals[INITIALIZED], 0, FALSE); - } else { - g_signal_connect (priv->proxy, "g-properties-changed", - G_CALLBACK (properties_changed), self); - query_properties (self); - } - g_object_unref (self); -} - -/*****************************************************************************/ - -static void -get_property (GObject *object, guint prop_id, - GValue *value, GParamSpec *pspec) -{ - NMBluezDevicePrivate *priv = NM_BLUEZ_DEVICE_GET_PRIVATE ((NMBluezDevice *) object); - - switch (prop_id) { - case PROP_PATH: - g_value_set_string (value, priv->path); - break; - case PROP_ADDRESS: - g_value_set_string (value, priv->address); - break; - case PROP_NAME: - g_value_set_string (value, priv->name); - break; - case PROP_CAPABILITIES: - g_value_set_uint (value, priv->capabilities); - break; - case PROP_USABLE: - g_value_set_boolean (value, priv->usable); - break; - case PROP_CONNECTED: - g_value_set_boolean (value, priv->connected); - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - break; - } -} - -static void -set_property (GObject *object, guint prop_id, - const GValue *value, GParamSpec *pspec) -{ - NMBluezDevicePrivate *priv = NM_BLUEZ_DEVICE_GET_PRIVATE ((NMBluezDevice *) 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 -nm_bluez_device_init (NMBluezDevice *self) -{ -} - -NMBluezDevice * -nm_bluez_device_new (GDBusConnection *dbus_connection, - const char *path, - NMSettings *settings) -{ - NMBluezDevice *self; - NMBluezDevicePrivate *priv; - - g_return_val_if_fail (path != NULL, NULL); - g_return_val_if_fail (NM_IS_SETTINGS (settings), NULL); - g_return_val_if_fail (G_IS_DBUS_CONNECTION (dbus_connection), NULL); - - self = (NMBluezDevice *) g_object_new (NM_TYPE_BLUEZ_DEVICE, - NM_BLUEZ_DEVICE_PATH, path, - NULL); - if (!self) - return NULL; - - _LOGD ("create NMBluezDevice"); - - priv = NM_BLUEZ_DEVICE_GET_PRIVATE (self); - - priv->settings = g_object_ref (settings); - - g_signal_connect (priv->settings, NM_SETTINGS_SIGNAL_CONNECTION_ADDED, G_CALLBACK (cp_connection_added), self); - g_signal_connect (priv->settings, NM_SETTINGS_SIGNAL_CONNECTION_REMOVED, G_CALLBACK (cp_connection_removed), self); - g_signal_connect (priv->settings, NM_SETTINGS_SIGNAL_CONNECTION_UPDATED, G_CALLBACK (cp_connection_updated), self); - - priv->dbus_connection = g_object_ref (dbus_connection); - - g_dbus_proxy_new (priv->dbus_connection, - G_DBUS_PROXY_FLAGS_NONE, - NULL, - NM_BLUEZ_SERVICE, - priv->path, - NM_BLUEZ5_DEVICE_INTERFACE, - NULL, - (GAsyncReadyCallback) on_proxy_acquired, - g_object_ref (self)); - - return self; -} - -static void -dispose (GObject *object) -{ - NMBluezDevice *self = NM_BLUEZ_DEVICE (object); - NMBluezDevicePrivate *priv = NM_BLUEZ_DEVICE_GET_PRIVATE (self); - NMSettingsConnection *to_delete = NULL; - - nm_clear_g_source (&priv->check_emit_usable_id); - - if (priv->pan_connection) { - /* Check whether we want to remove the created connection. If so, we take a reference - * and delete it at the end of dispose(). */ - if (NM_FLAGS_HAS (nm_settings_connection_get_flags (priv->pan_connection), - NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED)) - to_delete = g_object_ref (priv->pan_connection); - - priv->pan_connection = NULL; - } - -#if WITH_BLUEZ5_DUN - if (priv->b5_dun_context) { - nm_bluez5_dun_free (priv->b5_dun_context); - priv->b5_dun_context = NULL; - } -#endif - - if (priv->settings) { - g_signal_handlers_disconnect_by_func (priv->settings, cp_connection_added, self); - g_signal_handlers_disconnect_by_func (priv->settings, cp_connection_removed, self); - g_signal_handlers_disconnect_by_func (priv->settings, cp_connection_updated, self); - } - - g_slist_free_full (priv->connections, g_object_unref); - priv->connections = NULL; - - if (priv->adapter5) { - g_signal_handlers_disconnect_by_func (priv->adapter5, adapter5_on_properties_changed, self); - g_clear_object (&priv->adapter5); - } - - g_clear_object (&priv->dbus_connection); - - G_OBJECT_CLASS (nm_bluez_device_parent_class)->dispose (object); - - if (to_delete) { - _LOGD ("removing Bluetooth connection for NAP device: '%s' (%s)", - nm_settings_connection_get_id (to_delete), - nm_settings_connection_get_uuid (to_delete)); - nm_settings_connection_delete (to_delete, FALSE); - g_object_unref (to_delete); - } - - g_clear_object (&priv->settings); -} - -static void -finalize (GObject *object) -{ - NMBluezDevice *self = NM_BLUEZ_DEVICE (object); - NMBluezDevicePrivate *priv = NM_BLUEZ_DEVICE_GET_PRIVATE (self); - - _LOGD ("finalize NMBluezDevice"); - - g_free (priv->path); - g_free (priv->adapter_address); - g_free (priv->address); - g_free (priv->name); - g_free (priv->b4_iface); - - if (priv->proxy) - g_signal_handlers_disconnect_by_data (priv->proxy, object); - g_clear_object (&priv->proxy); - - G_OBJECT_CLASS (nm_bluez_device_parent_class)->finalize (object); -} - -static void -nm_bluez_device_class_init (NMBluezDeviceClass *config_class) -{ - GObjectClass *object_class = G_OBJECT_CLASS (config_class); - - object_class->get_property = get_property; - object_class->set_property = set_property; - object_class->dispose = dispose; - object_class->finalize = finalize; - - obj_properties[PROP_PATH] = - g_param_spec_string (NM_BLUEZ_DEVICE_PATH, "", "", - NULL, - G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | - G_PARAM_STATIC_STRINGS); - - obj_properties[PROP_ADDRESS] = - g_param_spec_string (NM_BLUEZ_DEVICE_ADDRESS, "", "", - NULL, - G_PARAM_READABLE | - G_PARAM_STATIC_STRINGS); - - obj_properties[PROP_NAME] = - g_param_spec_string (NM_BLUEZ_DEVICE_NAME, "", "", - NULL, - G_PARAM_READABLE | - G_PARAM_STATIC_STRINGS); - - obj_properties[PROP_CAPABILITIES] = - g_param_spec_uint (NM_BLUEZ_DEVICE_CAPABILITIES, "", "", - 0, G_MAXUINT, 0, - G_PARAM_READABLE | - G_PARAM_STATIC_STRINGS); - - obj_properties[PROP_USABLE] = - g_param_spec_boolean (NM_BLUEZ_DEVICE_USABLE, "", "", - FALSE, - G_PARAM_READABLE | - G_PARAM_STATIC_STRINGS); - - obj_properties[PROP_CONNECTED] = - g_param_spec_boolean (NM_BLUEZ_DEVICE_CONNECTED, "", "", - FALSE, - G_PARAM_READABLE | - G_PARAM_STATIC_STRINGS); - - g_object_class_install_properties (object_class, _PROPERTY_ENUMS_LAST, obj_properties); - - signals[INITIALIZED] = g_signal_new (NM_BLUEZ_DEVICE_INITIALIZED, - G_OBJECT_CLASS_TYPE (object_class), - G_SIGNAL_RUN_LAST, - 0, - NULL, NULL, NULL, - G_TYPE_NONE, 1, G_TYPE_BOOLEAN); - - signals[REMOVED] = g_signal_new (NM_BLUEZ_DEVICE_REMOVED, - G_OBJECT_CLASS_TYPE (object_class), - G_SIGNAL_RUN_LAST, - 0, - NULL, NULL, NULL, - G_TYPE_NONE, 0); -} - diff --git a/src/devices/bluetooth/nm-bluez-device.h b/src/devices/bluetooth/nm-bluez-device.h deleted file mode 100644 index 3bccea8a88..0000000000 --- a/src/devices/bluetooth/nm-bluez-device.h +++ /dev/null @@ -1,70 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0+ -/* NetworkManager -- Network link manager - * - * Copyright (C) 2009 - 2014 Red Hat, Inc. - */ - -#ifndef __NETWORKMANAGER_BLUEZ_DEVICE_H__ -#define __NETWORKMANAGER_BLUEZ_DEVICE_H__ - -#include "nm-connection.h" - -#define NM_TYPE_BLUEZ_DEVICE (nm_bluez_device_get_type ()) -#define NM_BLUEZ_DEVICE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_BLUEZ_DEVICE, NMBluezDevice)) -#define NM_BLUEZ_DEVICE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), NM_TYPE_BLUEZ_DEVICE, NMBluezDeviceClass)) -#define NM_IS_BLUEZ_DEVICE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_BLUEZ_DEVICE)) -#define NM_IS_BLUEZ_DEVICE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NM_TYPE_BLUEZ_DEVICE)) -#define NM_BLUEZ_DEVICE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), NM_TYPE_BLUEZ_DEVICE, NMBluezDeviceClass)) - -/* Properties */ -#define NM_BLUEZ_DEVICE_PATH "path" -#define NM_BLUEZ_DEVICE_ADDRESS "address" -#define NM_BLUEZ_DEVICE_NAME "name" -#define NM_BLUEZ_DEVICE_CAPABILITIES "capabilities" -#define NM_BLUEZ_DEVICE_USABLE "usable" -#define NM_BLUEZ_DEVICE_CONNECTED "connected" - -/* Signals */ -#define NM_BLUEZ_DEVICE_INITIALIZED "initialized" -#define NM_BLUEZ_DEVICE_REMOVED "removed" - -typedef struct _NMBluezDevice NMBluezDevice; -typedef struct _NMBluezDeviceClass NMBluezDeviceClass; - -GType nm_bluez_device_get_type (void); - -NMBluezDevice *nm_bluez_device_new (GDBusConnection *dbus_connection, - const char *path, - NMSettings *settings); - -const char *nm_bluez_device_get_path (NMBluezDevice *self); - -gboolean nm_bluez_device_get_initialized (NMBluezDevice *self); - -gboolean nm_bluez_device_get_usable (NMBluezDevice *self); - -const char *nm_bluez_device_get_address (NMBluezDevice *self); - -const char *nm_bluez_device_get_name (NMBluezDevice *self); - -guint32 nm_bluez_device_get_capabilities (NMBluezDevice *self); - -gboolean nm_bluez_device_get_connected (NMBluezDevice *self); - -typedef void (*NMBluezDeviceConnectCallback) (NMBluezDevice *self, - const char *device, - GError *error, - gpointer user_data); - -void -nm_bluez_device_connect_async (NMBluezDevice *self, - NMBluetoothCapabilities connection_bt_type, - GCancellable *cancellable, - NMBluezDeviceConnectCallback callback, - gpointer callback_user_data); - -void -nm_bluez_device_disconnect (NMBluezDevice *self); - -#endif /* __NETWORKMANAGER_BLUEZ_DEVICE_H__ */ - diff --git a/src/devices/bluetooth/nm-bluez-manager.c b/src/devices/bluetooth/nm-bluez-manager.c index cf9e521c74..22b40c0f85 100644 --- a/src/devices/bluetooth/nm-bluez-manager.c +++ b/src/devices/bluetooth/nm-bluez-manager.c @@ -6,52 +6,181 @@ #include "nm-default.h" +#include "nm-bluez-manager.h" + #include <signal.h> #include <stdlib.h> #include <gmodule.h> +#include "nm-glib-aux/nm-dbus-aux.h" +#include "nm-glib-aux/nm-c-list.h" +#include "nm-dbus-manager.h" #include "devices/nm-device-factory.h" #include "devices/nm-device-bridge.h" #include "nm-setting-bluetooth.h" #include "settings/nm-settings.h" -#include "nm-bluez5-manager.h" -#include "nm-bluez-device.h" #include "nm-bluez-common.h" #include "nm-device-bt.h" +#include "nm-manager.h" +#include "nm-bluez5-dun.h" #include "nm-core-internal.h" #include "platform/nm-platform.h" #include "nm-std-aux/nm-dbus-compat.h" /*****************************************************************************/ -#define NM_TYPE_BLUEZ_MANAGER (nm_bluez_manager_get_type ()) -#define NM_BLUEZ_MANAGER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_BLUEZ_MANAGER, NMBluezManager)) -#define NM_BLUEZ_MANAGER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), NM_TYPE_BLUEZ_MANAGER, NMBluezManagerClass)) -#define NM_IS_BLUEZ_MANAGER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_BLUEZ_MANAGER)) -#define NM_IS_BLUEZ_MANAGER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NM_TYPE_BLUEZ_MANAGER)) -#define NM_BLUEZ_MANAGER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), NM_TYPE_BLUEZ_MANAGER, NMBluezManagerClass)) +#if WITH_BLUEZ5_DUN +#define _NM_BT_CAPABILITY_SUPPORTED_DUN NM_BT_CAPABILITY_DUN +#else +#define _NM_BT_CAPABILITY_SUPPORTED_DUN NM_BT_CAPABILITY_NONE +#endif +#define _NM_BT_CAPABILITY_SUPPORTED (NM_BT_CAPABILITY_NAP | _NM_BT_CAPABILITY_SUPPORTED_DUN) + +typedef struct { + const char *bdaddr; + CList lst_head; + NMBluetoothCapabilities bt_type:8; + char bdaddr_data[]; +} ConnDataHead; + +typedef struct { + NMSettingsConnection *sett_conn; + ConnDataHead *cdata_hd; + CList lst; +} ConnDataElem; + +typedef struct { + GCancellable *ext_cancellable; + GCancellable *int_cancellable; + NMBtVTableRegisterCallback callback; + gpointer callback_user_data; + gulong ext_cancelled_id; +} NetworkServerRegisterReqData; typedef struct { + GCancellable *ext_cancellable; + GCancellable *int_cancellable; + NMBluezManagerConnectCb callback; + gpointer callback_user_data; + char *device_name; + gulong ext_cancelled_id; + guint timeout_id; + guint timeout_wait_connect_id; +} DeviceConnectReqData; +typedef struct { + const char *object_path; + + NMBluezManager *self; + + /* Fields name with "d_" prefix are purely cached values from BlueZ's + * ObjectManager D-Bus interface. There is no logic whatsoever about + * them. + */ + + CList process_change_lst; + + struct { + char *address; + } d_adapter; + + struct { + char *address; + char *name; + char *adapter; + } d_device; + + struct { + char *interface; + } d_network; + + struct { + CList lst; + char *adapter_address; + NMDevice *device_br; + NetworkServerRegisterReqData *r_req_data; + } x_network_server; + + struct { + NMSettingsConnection *panu_connection; + NMDeviceBt *device_bt; + DeviceConnectReqData *c_req_data; + NMBluez5DunContext *connect_dun_context; + gulong device_bt_signal_id; + } x_device; + + /* indicate whether the D-Bus object has the particular D-Bus interface. */ + bool d_has_adapter_iface:1; + bool d_has_device_iface:1; + bool d_has_network_iface:1; + bool d_has_network_server_iface:1; + + /* cached D-Bus properties for Device1 ("d_device*"). */ + NMBluetoothCapabilities d_device_capabilities:6; + bool d_device_connected:1; + bool d_device_paired:1; + + /* cached D-Bus properties for Network1 ("d_network*"). */ + bool d_network_connected:1; + + /* cached D-Bus properties for Adapter1 ("d_adapter*"). */ + bool d_adapter_powered:1; + + /* properties related to device ("x_device*"). */ + NMBluetoothCapabilities x_device_connect_bt_type:6; + bool x_device_is_usable:1; + bool x_device_is_connected:1; + + bool x_device_panu_connection_allow_create:1; + + /* flag to remember last time when we checked wether the object + * was a suitable adapter that is usable to a device. */ + bool was_usable_adapter_for_device_before:1; + + char _object_path_intern[]; +} BzDBusObj; + +typedef struct { + NMManager *manager; NMSettings *settings; - NMBluez5Manager *manager5; - guint watch_name_id; + GDBusConnection *dbus_connection; + + NMBtVTableNetworkServer vtable_network_server; + + GCancellable *name_owner_get_cancellable; + GCancellable *get_managed_objects_cancellable; + + GHashTable *bzobjs; + + char *name_owner; + + GHashTable *conn_data_heads; + GHashTable *conn_data_elems; + + CList network_server_lst_head; - GDBusProxy *introspect_proxy; - GCancellable *async_cancellable; + CList process_change_lst_head; + + guint name_owner_changed_id; + + guint managed_objects_changed_id; + + guint properties_changed_id; + + guint process_change_idle_id; + + bool settings_registered:1; } NMBluezManagerPrivate; -typedef struct { +struct _NMBluezManager { NMDeviceFactory parent; NMBluezManagerPrivate _priv; -} NMBluezManager; +}; -typedef struct { +struct _NMBluezManagerClass { NMDeviceFactoryClass parent; -} NMBluezManagerClass; - -static GType nm_bluez_manager_get_type (void); +}; G_DEFINE_TYPE (NMBluezManager, nm_bluez_manager, NM_TYPE_DEVICE_FACTORY); @@ -77,256 +206,2583 @@ nm_device_factory_create (GError **error) /*****************************************************************************/ -static void check_bluez_and_try_setup (NMBluezManager *self); +static NMBluetoothCapabilities +convert_uuids_to_capabilities (const char *const*strv) +{ + NMBluetoothCapabilities capabilities = NM_BT_CAPABILITY_NONE; + + if (strv) { + for (; strv[0]; strv++) { + gs_free char *s_part1 = NULL; + const char *str = strv[0]; + const char *s; + + s = strchr (str, '-'); + if (!s) + continue; + + s_part1 = g_strndup (str, s - str); + switch (g_ascii_strtoull (s_part1, NULL, 16)) { + case 0x1103: + capabilities |= NM_BT_CAPABILITY_DUN; + break; + case 0x1116: + capabilities |= NM_BT_CAPABILITY_NAP; + break; + default: + break; + } + } + } + + return capabilities; +} + +/*****************************************************************************/ + +static void _cleanup_for_name_owner (NMBluezManager *self); +static void _connect_disconnect (NMBluezManager *self, + BzDBusObj *bzobj, + const char *reason); +static gboolean _bzobjs_network_server_is_usable (const BzDBusObj *bzobj, + gboolean require_powered); +static gboolean _bzobjs_is_dead (const BzDBusObj *bzobj); +static gboolean _bzobjs_device_is_usable (const BzDBusObj *bzobj, + BzDBusObj **out_adapter_bzobj, + gboolean *out_create_panu_connection); +static gboolean _bzobjs_adapter_is_usable_for_device (const BzDBusObj *bzobj); +static ConnDataHead *_conn_track_find_head (NMBluezManager *self, + NMBluetoothCapabilities bt_type, + const char *bdaddr); +static void _process_change_idle_schedule (NMBluezManager *self, + BzDBusObj *bzobj); +static void _network_server_unregister_bridge (NMBluezManager *self, + BzDBusObj *bzobj, + const char *reason); +static gboolean _connect_timeout_wait_connected_cb (gpointer user_data); + +/*****************************************************************************/ + +static void +_dbus_call_complete_cb_nop (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + /* we don't do anything at all. The only reason to register this + * callback is so that GDBusConnection keeps the cancellable alive + * long enough until the call completes. + * + * Note that this cancellable in turn is registered via + * nm_shutdown_wait_obj_register_*(), to block shutdown until + * we are done. */ +} + +/*****************************************************************************/ + +static void +_network_server_register_req_data_complete (NetworkServerRegisterReqData *r_req_data, + GError *error) +{ + nm_clear_g_signal_handler (r_req_data->ext_cancellable, &r_req_data->ext_cancelled_id); + + nm_clear_g_cancellable (&r_req_data->int_cancellable); + + if (r_req_data->callback) { + gs_free GError *error_cancelled = NULL; + + if (g_cancellable_set_error_if_cancelled (r_req_data->ext_cancellable, &error_cancelled)) + error = error_cancelled; + + r_req_data->callback (error, r_req_data->callback_user_data); + } + + g_object_unref (r_req_data->ext_cancellable); + nm_g_slice_free (r_req_data); +} + +static void +_device_connect_req_data_complete (DeviceConnectReqData *c_req_data, + NMBluezManager *self, + const char *device_name, + GError *error) +{ + nm_assert ((!!device_name) != (!!error)); + + nm_clear_g_signal_handler (c_req_data->ext_cancellable, &c_req_data->ext_cancelled_id); + + nm_clear_g_cancellable (&c_req_data->int_cancellable); + nm_clear_g_source (&c_req_data->timeout_id); + nm_clear_g_source (&c_req_data->timeout_wait_connect_id); + + if (c_req_data->callback) { + gs_free GError *error_cancelled = NULL; + + if (g_cancellable_set_error_if_cancelled (c_req_data->ext_cancellable, &error_cancelled)) { + error = error_cancelled; + device_name = NULL; + } + + c_req_data->callback (self, TRUE, device_name, error, c_req_data->callback_user_data); + } + + g_object_unref (c_req_data->ext_cancellable); + nm_clear_g_free (&c_req_data->device_name); + nm_g_slice_free (c_req_data); +} /*****************************************************************************/ -struct AsyncData { +static BzDBusObj * +_bz_dbus_obj_new (NMBluezManager *self, + const char *object_path) +{ + BzDBusObj *bzobj; + gsize l; + + nm_assert (NM_IS_BLUEZ_MANAGER (self)); + + l = strlen (object_path) + 1; + + bzobj = g_malloc (sizeof (BzDBusObj) + l); + *bzobj = (BzDBusObj) { + .object_path = bzobj->_object_path_intern, + .self = self, + .x_network_server.lst = C_LIST_INIT (bzobj->x_network_server.lst), + .process_change_lst = C_LIST_INIT (bzobj->process_change_lst), + .x_device_panu_connection_allow_create = TRUE, + }; + memcpy (bzobj->_object_path_intern, object_path, l); + + return bzobj; +} + +static void +_bz_dbus_obj_free (BzDBusObj *bzobj) +{ + nm_assert (bzobj); + nm_assert (NM_IS_BLUEZ_MANAGER (bzobj->self)); + nm_assert (!bzobj->x_network_server.device_br); + nm_assert (!bzobj->x_network_server.r_req_data); + nm_assert (!bzobj->x_device.c_req_data); + + c_list_unlink_stale (&bzobj->process_change_lst); + c_list_unlink_stale (&bzobj->x_network_server.lst); + g_free (bzobj->x_network_server.adapter_address); + g_free (bzobj->d_adapter.address); + g_free (bzobj->d_network.interface); + g_free (bzobj->d_device.address); + g_free (bzobj->d_device.name); + g_free (bzobj->d_device.adapter); + g_free (bzobj); +} + +/*****************************************************************************/ + +static const char * +_bzobj_to_string (const BzDBusObj *bzobj, char *buf, gsize len) +{ + char *buf0 = buf; + const char *prefix = ""; + gboolean device_is_usable; + gboolean create_panu_connection = FALSE; + gboolean network_server_is_usable; + char sbuf_cap[100]; + + if (len > 0) + buf[0] = '\0'; + + if (bzobj->d_has_adapter_iface) { + nm_utils_strbuf_append_str (&buf, &len, prefix); + prefix = ", "; + nm_utils_strbuf_append_str (&buf, &len, "Adapter1 {"); + if (bzobj->d_adapter.address) { + nm_utils_strbuf_append (&buf, &len, " d.address: \"%s\"", bzobj->d_adapter.address); + if (bzobj->d_adapter_powered) + nm_utils_strbuf_append_str (&buf, &len, ","); + } + if (bzobj->d_adapter_powered) + nm_utils_strbuf_append (&buf, &len, " d.powered: 1"); + nm_utils_strbuf_append_str (&buf, &len, " }"); + } + + if (bzobj->d_has_device_iface) { + const char *prefix1 = ""; + + nm_utils_strbuf_append_str (&buf, &len, prefix); + prefix = ", "; + nm_utils_strbuf_append_str (&buf, &len, "Device1 {"); + if (bzobj->d_device.address) { + nm_utils_strbuf_append (&buf, &len, "%s d.address: \"%s\"", prefix1, bzobj->d_device.address); + prefix1 = ","; + } + if (bzobj->d_device.name) { + nm_utils_strbuf_append (&buf, &len, "%s d.name: \"%s\"", prefix1, bzobj->d_device.name); + prefix1 = ","; + } + if (bzobj->d_device.adapter) { + nm_utils_strbuf_append (&buf, &len, "%s d.adapter: \"%s\"", prefix1, bzobj->d_device.adapter); + prefix1 = ","; + } + if (bzobj->d_device_capabilities != NM_BT_CAPABILITY_NONE) { + nm_utils_strbuf_append (&buf, &len, "%s d.capabilities: \"%s\"", + prefix1, + nm_bluetooth_capability_to_string (bzobj->d_device_capabilities, sbuf_cap, sizeof (sbuf_cap))); + prefix1 = ","; + } + if (bzobj->d_device_connected) { + nm_utils_strbuf_append (&buf, &len, "%s d.connected: 1", prefix1); + prefix1 = ","; + } + if (bzobj->d_device_paired) { + nm_utils_strbuf_append (&buf, &len, "%s d.paired: 1", prefix1); + prefix1 = ","; + } + nm_utils_strbuf_append_str (&buf, &len, " }"); + } + + network_server_is_usable = _bzobjs_network_server_is_usable (bzobj, TRUE); + + if ( bzobj->d_has_network_server_iface + || network_server_is_usable != (!c_list_is_empty (&bzobj->x_network_server.lst)) + || !c_list_is_empty (&bzobj->x_network_server.lst) + || !nm_streq0 (bzobj->d_has_adapter_iface ? bzobj->d_adapter.address : NULL, bzobj->x_network_server.adapter_address) + || bzobj->x_network_server.device_br + || bzobj->x_network_server.r_req_data) { + + nm_utils_strbuf_append_str (&buf, &len, prefix); + prefix = ", "; + + nm_utils_strbuf_append (&buf, &len, "NetworkServer1 { "); + + if (!bzobj->d_has_network_server_iface) + nm_utils_strbuf_append (&buf, &len, " has-d-iface: 0, "); + + if (network_server_is_usable != (!c_list_is_empty (&bzobj->x_network_server.lst))) + nm_utils_strbuf_append (&buf, &len, "usable: %d, used: %d", !!network_server_is_usable, !network_server_is_usable); + else if (network_server_is_usable) + nm_utils_strbuf_append (&buf, &len, "used: 1"); + else + nm_utils_strbuf_append (&buf, &len, "usable: 0"); + + if (!nm_streq0 (bzobj->d_has_adapter_iface ? bzobj->d_adapter.address : NULL, bzobj->x_network_server.adapter_address)) { + if (bzobj->x_network_server.adapter_address) + nm_utils_strbuf_append (&buf, &len, ", adapter-address: \"%s\"", bzobj->x_network_server.adapter_address); + else + nm_utils_strbuf_append (&buf, &len, ", adapter-address: <NULL>"); + } + + if (bzobj->x_network_server.device_br) + nm_utils_strbuf_append (&buf, &len, ", bridge-device: 1"); + + if (bzobj->x_network_server.r_req_data) + nm_utils_strbuf_append (&buf, &len, ", register-in-progress: 1"); + + nm_utils_strbuf_append_str (&buf, &len, " }"); + } + + device_is_usable = _bzobjs_device_is_usable (bzobj, NULL, &create_panu_connection); + + if ( bzobj->d_has_network_iface + || bzobj->d_network.interface + || bzobj->d_network_connected + || create_panu_connection + || bzobj->x_device.panu_connection + || device_is_usable != bzobj->x_device_is_usable + || bzobj->x_device.device_bt + || bzobj->x_device_connect_bt_type != NM_BT_CAPABILITY_NONE + || bzobj->x_device.connect_dun_context + || bzobj->x_device.c_req_data + || bzobj->x_device_is_connected != bzobj->d_network_connected) { + + nm_utils_strbuf_append_str (&buf, &len, prefix); + prefix = ", "; + nm_utils_strbuf_append_str (&buf, &len, "Network1 {"); + if (bzobj->d_network.interface) + nm_utils_strbuf_append (&buf, &len, " d.interface: \"%s\", ", bzobj->d_network.interface); + if (bzobj->d_network_connected) + nm_utils_strbuf_append (&buf, &len, " d.connected: %d, ", !!bzobj->d_network_connected); + if (!bzobj->d_has_network_iface) + nm_utils_strbuf_append (&buf, &len, " has-d-iface: 0, "); + if (device_is_usable != bzobj->x_device_is_usable) + nm_utils_strbuf_append (&buf, &len, " usable: %d, used: %d", !!device_is_usable, !device_is_usable); + else if (device_is_usable) + nm_utils_strbuf_append (&buf, &len, " used: 1"); + else + nm_utils_strbuf_append (&buf, &len, " usable: 0"); + + if (create_panu_connection) + nm_utils_strbuf_append (&buf, &len, ", create-panu-connection: 1"); + + if (bzobj->x_device.panu_connection) + nm_utils_strbuf_append (&buf, &len, ", has-panu-connection: 1"); + + if (bzobj->x_device.device_bt) + nm_utils_strbuf_append (&buf, &len, ", has-device: 1"); + + if ( bzobj->x_device_connect_bt_type != NM_BT_CAPABILITY_NONE + || bzobj->x_device.connect_dun_context) { + nm_utils_strbuf_append (&buf, &len, ", connect: %s%s", + nm_bluetooth_capability_to_string (bzobj->x_device_connect_bt_type, sbuf_cap, sizeof (sbuf_cap)), + bzobj->x_device.connect_dun_context ? ",with-dun-context" : ""); + } + + if (bzobj->x_device.c_req_data) + nm_utils_strbuf_append (&buf, &len, ", connecting: 1"); + + if (bzobj->x_device_is_connected != bzobj->d_network_connected) + nm_utils_strbuf_append (&buf, &len, ", connected: %d", !!bzobj->x_device_is_connected); + + nm_utils_strbuf_append_str (&buf, &len, " }"); + } + + if (_bzobjs_is_dead (bzobj)) { + nm_utils_strbuf_append_str (&buf, &len, prefix); + prefix = ", "; + nm_utils_strbuf_append_str (&buf, &len, "dead: 1"); + } + + if (!c_list_is_empty (&bzobj->process_change_lst)) { + nm_utils_strbuf_append_str (&buf, &len, prefix); + prefix = ", "; + nm_utils_strbuf_append (&buf, &len, "change-pending-on-idle: 1"); + } + + if (_bzobjs_adapter_is_usable_for_device (bzobj) != bzobj->was_usable_adapter_for_device_before) { + nm_utils_strbuf_append_str (&buf, &len, prefix); + prefix = ", "; + nm_utils_strbuf_append (&buf, &len, "change-usable-adapter-for-device: 1"); + } + + return buf0; +} + +#define _LOG_bzobj(bzobj, context) \ + G_STMT_START { \ + const BzDBusObj *const _bzobj = (bzobj); \ + char _buf[500]; \ + \ + _LOGT ("change %-21s %s : { %s }", \ + (context), \ + _bzobj->object_path, \ + _bzobj_to_string (_bzobj, _buf, sizeof (_buf))); \ + } G_STMT_END + +static gboolean +_bzobjs_is_dead (const BzDBusObj *bzobj) +{ + return !bzobj->d_has_adapter_iface + && !bzobj->d_has_device_iface + && !bzobj->d_has_network_iface + && !bzobj->d_has_network_server_iface + && c_list_is_empty (&bzobj->process_change_lst); +} + +static BzDBusObj * +_bzobjs_get (NMBluezManager *self, const char *object_path) +{ + return g_hash_table_lookup (NM_BLUEZ_MANAGER_GET_PRIVATE (self)->bzobjs, &object_path); +} + +static BzDBusObj * +_bzobjs_add (NMBluezManager *self, + const char *object_path) +{ + NMBluezManagerPrivate *priv = NM_BLUEZ_MANAGER_GET_PRIVATE (self); + BzDBusObj *bzobj; + + bzobj = _bz_dbus_obj_new (self, object_path); + if (!g_hash_table_add (priv->bzobjs, bzobj)) + nm_assert_not_reached (); + return bzobj; +} + +static void +_bzobjs_del (BzDBusObj *bzobj) +{ + nm_assert (bzobj); + nm_assert (bzobj == _bzobjs_get (bzobj->self, bzobj->object_path)); + + if (!g_hash_table_remove (NM_BLUEZ_MANAGER_GET_PRIVATE (bzobj->self)->bzobjs, bzobj)) + nm_assert_not_reached (); +} + +static void +_bzobjs_del_if_dead (BzDBusObj *bzobj) +{ + if (_bzobjs_is_dead (bzobj)) + _bzobjs_del (bzobj); +} + +static BzDBusObj * +_bzobjs_init (NMBluezManager *self, BzDBusObj **inout, const char *object_path) +{ + nm_assert (NM_IS_BLUEZ_MANAGER (self)); + nm_assert (object_path); + nm_assert (inout); + + if (!*inout) { + *inout = _bzobjs_get (self, object_path); + if (!*inout) + *inout = _bzobjs_add (self, object_path); + } + + nm_assert (nm_streq ((*inout)->object_path, object_path)); + nm_assert (*inout == _bzobjs_get (self, object_path)); + return *inout; +} + +static gboolean +_bzobjs_adapter_is_usable_for_device (const BzDBusObj *bzobj) +{ + return bzobj->d_has_adapter_iface + && bzobj->d_adapter.address + && bzobj->d_adapter_powered; +} + +static gboolean +_bzobjs_device_is_usable (const BzDBusObj *bzobj, + BzDBusObj **out_adapter_bzobj, + gboolean *out_create_panu_connection) +{ NMBluezManager *self; - GCancellable *async_cancellable; -}; + NMBluezManagerPrivate *priv; + gboolean usable_dun = FALSE; + gboolean usable_nap = FALSE; + BzDBusObj *bzobj_adapter; + gboolean create_panu_connection = FALSE; + + if ( !bzobj->d_has_device_iface + || !NM_FLAGS_ANY ((NMBluetoothCapabilities) bzobj->d_device_capabilities, _NM_BT_CAPABILITY_SUPPORTED) + || !bzobj->d_device.name + || !bzobj->d_device.address + || !bzobj->d_device_paired + || !bzobj->d_device.adapter) + goto out_unusable; + + self = bzobj->self; + + priv = NM_BLUEZ_MANAGER_GET_PRIVATE (self); + + if (!priv->settings_registered) + goto out_unusable; + + bzobj_adapter = _bzobjs_get (self, bzobj->d_device.adapter); + if ( !bzobj_adapter + || !_bzobjs_adapter_is_usable_for_device (bzobj_adapter)) + goto out_unusable; -static struct AsyncData * -async_data_pack (NMBluezManager *self) +#if WITH_BLUEZ5_DUN + if (NM_FLAGS_HAS (bzobj->d_device_capabilities, NM_BT_CAPABILITY_DUN)) { + if (_conn_track_find_head (self, NM_BT_CAPABILITY_DUN, bzobj->d_device.address)) + usable_dun = TRUE; + } +#endif + + if (NM_FLAGS_HAS (bzobj->d_device_capabilities, NM_BT_CAPABILITY_NAP)) { + if (!bzobj->d_has_network_iface) + usable_nap = FALSE; + else if (_conn_track_find_head (self, NM_BT_CAPABILITY_NAP, bzobj->d_device.address)) + usable_nap = TRUE; + else if (bzobj->x_device_panu_connection_allow_create) { + /* We didn't yet try to create a connection. Presume we are going to create + * it when the time comes... */ + usable_nap = TRUE; + create_panu_connection = TRUE; + } + } + + if ( !usable_dun + && !usable_nap) { + if ( bzobj->x_device.device_bt + && nm_device_get_state (NM_DEVICE (bzobj->x_device.device_bt)) > NM_DEVICE_STATE_DISCONNECTED) { + /* The device is still activated... the absence of a profile does not + * render it unusable (yet). But since there is no more profile, the + * device is probably about to disconnect. */ + } else + goto out_unusable; + } + + NM_SET_OUT (out_create_panu_connection, create_panu_connection); + NM_SET_OUT (out_adapter_bzobj, bzobj_adapter); + return TRUE; + +out_unusable: + NM_SET_OUT (out_create_panu_connection, FALSE); + NM_SET_OUT (out_adapter_bzobj, NULL); + return FALSE; +} + +static gboolean +_bzobjs_device_is_connected (const BzDBusObj *bzobj) +{ + nm_assert (_bzobjs_device_is_usable (bzobj, NULL, NULL)); + + if ( !bzobj->d_has_device_iface + || !bzobj->d_device_connected) + return FALSE; + + if ( bzobj->d_has_network_iface + && bzobj->d_network_connected) + return TRUE; + if (bzobj->x_device.connect_dun_context) { + /* As long as we have a dun-context, we consider it connected. + * + * We require NMDeviceBt to try to connect to the modem, and if that fails, + * it will disconnect. */ + return TRUE; + } + return FALSE; +} + +static gboolean +_bzobjs_network_server_is_usable (const BzDBusObj *bzobj, + gboolean require_powered) +{ + return bzobj->d_has_network_server_iface + && bzobj->d_has_adapter_iface + && bzobj->d_adapter.address + && ( !require_powered + || bzobj->d_adapter_powered); +} + +/*****************************************************************************/ + +static ConnDataHead * +_conn_data_head_new (NMBluetoothCapabilities bt_type, + const char *bdaddr) +{ + ConnDataHead *cdata_hd; + gsize l; + + nm_assert (NM_IN_SET (bt_type, NM_BT_CAPABILITY_DUN, + NM_BT_CAPABILITY_NAP)); + nm_assert (bdaddr); + + l = strlen (bdaddr) + 1; + cdata_hd = g_malloc (sizeof (ConnDataHead) + l); + *cdata_hd = (ConnDataHead) { + .bdaddr = cdata_hd->bdaddr_data, + .lst_head = C_LIST_INIT (cdata_hd->lst_head), + .bt_type = bt_type, + }; + memcpy (cdata_hd->bdaddr_data, bdaddr, l); + + nm_assert (cdata_hd->bt_type == bt_type); + + return cdata_hd; +} + +static guint +_conn_data_head_hash (gconstpointer ptr) +{ + const ConnDataHead *cdata_hd = ptr; + NMHashState h; + + nm_hash_init (&h, 520317467u); + nm_hash_update_val (&h, (NMBluetoothCapabilities) cdata_hd->bt_type); + nm_hash_update_str (&h, cdata_hd->bdaddr); + return nm_hash_complete (&h); +} + +static gboolean +_conn_data_head_equal (gconstpointer a, gconstpointer b) +{ + const ConnDataHead *cdata_hd_a = a; + const ConnDataHead *cdata_hd_b = b; + + return cdata_hd_a->bt_type == cdata_hd_b->bt_type + && nm_streq (cdata_hd_a->bdaddr, cdata_hd_b->bdaddr); +} + +static ConnDataHead * +_conn_track_find_head (NMBluezManager *self, + NMBluetoothCapabilities bt_type, + const char *bdaddr) +{ + ConnDataHead cdata_hd = { + .bt_type = bt_type, + .bdaddr = bdaddr, + }; + + return g_hash_table_lookup (NM_BLUEZ_MANAGER_GET_PRIVATE (self)->conn_data_heads, &cdata_hd); +} + +static ConnDataElem * +_conn_track_find_elem (NMBluezManager *self, + NMSettingsConnection *sett_conn) +{ + G_STATIC_ASSERT (G_STRUCT_OFFSET (ConnDataElem, sett_conn) == 0); + + return g_hash_table_lookup (NM_BLUEZ_MANAGER_GET_PRIVATE (self)->conn_data_elems, &sett_conn); +} + +static gboolean +_conn_track_is_relevant_connection (NMConnection *connection, + NMBluetoothCapabilities *out_bt_type, + const char **out_bdaddr) +{ + NMSettingBluetooth *s_bt; + NMBluetoothCapabilities bt_type; + const char *bdaddr; + const char *b_type; + + s_bt = nm_connection_get_setting_bluetooth (connection); + if (!s_bt) + return FALSE; + + if (!nm_connection_is_type (connection, NM_SETTING_BLUETOOTH_SETTING_NAME)) + return FALSE; + + bdaddr = nm_setting_bluetooth_get_bdaddr (s_bt); + if (!bdaddr) + return FALSE; + + b_type = nm_setting_bluetooth_get_connection_type (s_bt); + + if (nm_streq (b_type, NM_SETTING_BLUETOOTH_TYPE_DUN)) + bt_type = NM_BT_CAPABILITY_DUN; + else if (nm_streq (b_type, NM_SETTING_BLUETOOTH_TYPE_PANU)) + bt_type = NM_BT_CAPABILITY_NAP; + else + return FALSE; + + NM_SET_OUT (out_bt_type, bt_type); + NM_SET_OUT (out_bdaddr, bdaddr); + return TRUE; +} + +static gboolean +_conn_track_is_relevant_sett_conn (NMSettingsConnection *sett_conn, + NMBluetoothCapabilities *out_bt_type, + const char **out_bdaddr) +{ + NMConnection *connection; + + connection = nm_settings_connection_get_connection (sett_conn); + if (!connection) + return FALSE; + + return _conn_track_is_relevant_connection (connection, out_bt_type, out_bdaddr); +} + +static gboolean +_conn_track_is_relevant_for_sett_conn (NMSettingsConnection *sett_conn, + NMBluetoothCapabilities bt_type, + const char *bdaddr) +{ + NMBluetoothCapabilities x_bt_type; + const char *x_bdaddr; + + return bdaddr + && _conn_track_is_relevant_sett_conn (sett_conn, &x_bt_type, &x_bdaddr) + && x_bt_type == bt_type + && nm_streq (x_bdaddr, bdaddr); +} + +static void +_conn_track_schedule_notify (NMBluezManager *self, + NMBluetoothCapabilities bt_type, + const char *bdaddr) +{ + NMBluezManagerPrivate *priv = NM_BLUEZ_MANAGER_GET_PRIVATE (self); + GHashTableIter iter; + BzDBusObj *bzobj; + + g_hash_table_iter_init (&iter, priv->bzobjs); + while (g_hash_table_iter_next (&iter, (gpointer *) &bzobj, NULL)) { + gboolean device_is_usable; + + device_is_usable = _bzobjs_device_is_usable (bzobj, NULL, NULL); + if (bzobj->x_device_is_usable != device_is_usable) + _process_change_idle_schedule (self, bzobj); + } +} + +static void +_conn_track_update (NMBluezManager *self, + NMSettingsConnection *sett_conn, + gboolean track, + gboolean *out_changed, + gboolean *out_changed_usable, + ConnDataElem **out_conn_data_elem) +{ + NMBluezManagerPrivate *priv = NM_BLUEZ_MANAGER_GET_PRIVATE (self); + ConnDataHead *cdata_hd; + ConnDataElem *cdata_el; + ConnDataElem *cdata_el_remove = NULL; + NMBluetoothCapabilities bt_type; + const char *bdaddr; + gboolean changed = FALSE; + gboolean changed_usable = FALSE; + char sbuf_cap[100]; + + nm_assert (NM_IS_SETTINGS_CONNECTION (sett_conn)); + + cdata_el = _conn_track_find_elem (self, sett_conn); + + if (track) + track = _conn_track_is_relevant_sett_conn (sett_conn, &bt_type, &bdaddr); + + if (!track) { + cdata_el_remove = g_steal_pointer (&cdata_el); + goto out_remove; + } + + if (cdata_el) { + cdata_hd = cdata_el->cdata_hd; + if ( cdata_hd->bt_type != bt_type + || !nm_streq (cdata_hd->bdaddr, bdaddr)) + cdata_el_remove = g_steal_pointer (&cdata_el); + } + + if (!cdata_el) { + _LOGT ("connecton: track for %s, %s: %s (%s)", + nm_bluetooth_capability_to_string (bt_type, sbuf_cap, sizeof (sbuf_cap)), + bdaddr, + nm_settings_connection_get_uuid (sett_conn), + nm_settings_connection_get_id (sett_conn)); + changed = TRUE; + cdata_hd = _conn_track_find_head (self, bt_type, bdaddr); + if (!cdata_hd) { + changed_usable = TRUE; + cdata_hd = _conn_data_head_new (bt_type, bdaddr); + if (!g_hash_table_add (priv->conn_data_heads, cdata_hd)) + nm_assert_not_reached (); + _conn_track_schedule_notify (self, bt_type, bdaddr); + } + cdata_el = g_slice_new (ConnDataElem); + cdata_el->sett_conn = sett_conn; + cdata_el->cdata_hd = cdata_hd; + c_list_link_tail (&cdata_hd->lst_head, &cdata_el->lst); + if (!g_hash_table_add (priv->conn_data_elems, cdata_el)) + nm_assert_not_reached (); + } + +out_remove: + if (cdata_el_remove) { + GHashTableIter iter; + BzDBusObj *bzobj; + + _LOGT ("connecton: untrack for %s, %s: %s (%s)", + nm_bluetooth_capability_to_string (cdata_el_remove->cdata_hd->bt_type, sbuf_cap, sizeof (sbuf_cap)), + cdata_el_remove->cdata_hd->bdaddr, + nm_settings_connection_get_uuid (sett_conn), + nm_settings_connection_get_id (sett_conn)); + + g_hash_table_iter_init (&iter, priv->bzobjs); + while (g_hash_table_iter_next (&iter, (gpointer *) &bzobj, NULL)) { + if (bzobj->x_device.panu_connection == sett_conn) + bzobj->x_device.panu_connection = NULL; + } + + changed = TRUE; + cdata_hd = cdata_el_remove->cdata_hd; + c_list_unlink_stale (&cdata_el_remove->lst); + if (!g_hash_table_remove (priv->conn_data_elems, cdata_el_remove)) + nm_assert_not_reached (); + if (c_list_is_empty (&cdata_hd->lst_head)) { + changed_usable = TRUE; + _conn_track_schedule_notify (self, cdata_hd->bt_type, cdata_hd->bdaddr); + if (!g_hash_table_remove (priv->conn_data_heads, cdata_hd)) + nm_assert_not_reached (); + } + } + + NM_SET_OUT (out_changed, changed); + NM_SET_OUT (out_changed_usable, changed_usable); + NM_SET_OUT (out_conn_data_elem, cdata_el); +} + +/*****************************************************************************/ + +static void +cp_connection_added (NMSettings *settings, + NMSettingsConnection *sett_conn, + NMBluezManager *self) { - struct AsyncData *data = g_new (struct AsyncData, 1); + _conn_track_update (self, sett_conn, TRUE, NULL, NULL, NULL); +} - data->self = self; - data->async_cancellable = g_object_ref (NM_BLUEZ_MANAGER_GET_PRIVATE (self)->async_cancellable); - return data; +static void +cp_connection_updated (NMSettings *settings, + NMSettingsConnection *sett_conn, + guint update_reason_u, + NMBluezManager *self) +{ + _conn_track_update (self, sett_conn, TRUE, NULL, NULL, NULL); +} + +static void +cp_connection_removed (NMSettings *settings, + NMSettingsConnection *sett_conn, + NMBluezManager *self) +{ + _conn_track_update (self, sett_conn, FALSE, NULL, NULL, NULL); } +/*****************************************************************************/ + static NMBluezManager * -async_data_unpack (struct AsyncData *async_data) +_network_server_get_bluez_manager (const NMBtVTableNetworkServer *vtable_network_server) { - NMBluezManager *self = g_cancellable_is_cancelled (async_data->async_cancellable) - ? NULL : async_data->self; + NMBluezManager *self; + + self = (NMBluezManager *) (((char *) vtable_network_server) - G_STRUCT_OFFSET (NMBluezManager, _priv.vtable_network_server)); + + g_return_val_if_fail (NM_IS_BLUEZ_MANAGER (self), NULL); - g_object_unref (async_data->async_cancellable); - g_free (async_data); return self; } -/** - * Cancel any current attempt to detect the version and cleanup - * the related fields. - **/ +static BzDBusObj * +_network_server_find_has_device (NMBluezManagerPrivate *priv, + NMDevice *device) +{ + BzDBusObj *bzobj; + + c_list_for_each_entry (bzobj, &priv->network_server_lst_head, x_network_server.lst) { + if (bzobj->x_network_server.device_br == device) + return bzobj; + } + return NULL; +} + +static BzDBusObj * +_network_server_find_available (NMBluezManagerPrivate *priv, + const char *addr, + NMDevice *device_accept_busy) +{ + BzDBusObj *bzobj; + + c_list_for_each_entry (bzobj, &priv->network_server_lst_head, x_network_server.lst) { + if (bzobj->x_network_server.device_br) { + if (bzobj->x_network_server.device_br != device_accept_busy) + continue; + } + if ( addr + && !nm_streq (addr, bzobj->d_adapter.address)) + continue; + nm_assert (!bzobj->x_network_server.r_req_data); + return bzobj; + } + return NULL; +} + +static gboolean +_network_server_vt_is_available (const NMBtVTableNetworkServer *vtable, + const char *addr, + NMDevice *device_accept_busy) +{ + NMBluezManager *self = _network_server_get_bluez_manager (vtable); + NMBluezManagerPrivate *priv = NM_BLUEZ_MANAGER_GET_PRIVATE (self); + + return !!_network_server_find_available (priv, addr, device_accept_busy); +} + static void -cleanup_checking (NMBluezManager *self, gboolean do_unwatch_name) +_network_server_register_cb (GObject *source_object, + GAsyncResult *res, + gpointer user_data) { + gs_unref_variant GVariant *ret = NULL; + gs_free_error GError *error = NULL; + BzDBusObj *bzobj; + + ret = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source_object), res, &error); + if ( !ret + && nm_utils_error_is_cancelled (error, FALSE)) + return; + + bzobj = user_data; + + if (!ret) { + _LOGT ("NAP: [%s]: registering failed: %s", bzobj->object_path, error->message); + } else + _LOGT ("NAP: [%s]: registration successful", bzobj->object_path); + + g_clear_object (&bzobj->x_network_server.r_req_data->int_cancellable); + _network_server_register_req_data_complete (g_steal_pointer (&bzobj->x_network_server.r_req_data), error); +} + +static void +_network_server_register_cancelled_cb (GCancellable *cancellable, + BzDBusObj *bzobj) +{ + _network_server_unregister_bridge (bzobj->self, bzobj, "registration cancelled"); +} + +static gboolean +_network_server_vt_register_bridge (const NMBtVTableNetworkServer *vtable, + const char *addr, + NMDevice *device, + GCancellable *cancellable, + NMBtVTableRegisterCallback callback, + gpointer callback_user_data, + GError **error) +{ + NMBluezManager *self = _network_server_get_bluez_manager (vtable); NMBluezManagerPrivate *priv = NM_BLUEZ_MANAGER_GET_PRIVATE (self); + NetworkServerRegisterReqData *r_req_data; + BzDBusObj *bzobj; + const char *ifname; + + g_return_val_if_fail (NM_IS_DEVICE (device), FALSE); + g_return_val_if_fail (G_IS_CANCELLABLE (cancellable), FALSE); + + nm_assert (!g_cancellable_is_cancelled (cancellable)); + nm_assert (!_network_server_find_has_device (priv, device)); + + ifname = nm_device_get_iface (device); + g_return_val_if_fail (ifname, FALSE); + + g_return_val_if_fail (ifname, FALSE); + + bzobj = _network_server_find_available (priv, addr, NULL); + if (!bzobj) { + /* The device checked that a network server is available, before + * starting the activation, but for some reason it no longer is. + * Indicate that the activation should not proceed. */ + if (addr) { + nm_utils_error_set (error, NM_UTILS_ERROR_UNKNOWN, + "adapter %s is not available for %s", + addr, ifname); + } else { + nm_utils_error_set (error, NM_UTILS_ERROR_UNKNOWN, + "no adapter available for %s", + ifname); + } + return FALSE; + } + + _LOGD ("NAP: [%s]: registering \"%s\" on adapter %s", + bzobj->object_path, + ifname, + bzobj->d_adapter.address); + + r_req_data = g_slice_new (NetworkServerRegisterReqData); + *r_req_data = (NetworkServerRegisterReqData) { + .int_cancellable = g_cancellable_new (), + .ext_cancellable = g_object_ref (cancellable), + .callback = callback, + .callback_user_data = callback_user_data, + .ext_cancelled_id = g_signal_connect (cancellable, + "cancelled", + G_CALLBACK (_network_server_register_cancelled_cb), + bzobj), + }; + + bzobj->x_network_server.device_br = g_object_ref (device); + bzobj->x_network_server.r_req_data = r_req_data; + + g_dbus_connection_call (priv->dbus_connection, + priv->name_owner, + bzobj->object_path, + NM_BLUEZ5_NETWORK_SERVER_INTERFACE, + "Register", + g_variant_new ("(ss)", + BLUETOOTH_CONNECT_NAP, + ifname), + NULL, + G_DBUS_CALL_FLAGS_NO_AUTO_START, + -1, + bzobj->x_network_server.r_req_data->int_cancellable, + _network_server_register_cb, + bzobj); + return TRUE; +} + +static void +_network_server_unregister_bridge_complete_on_idle_cb (gpointer user_data, + GCancellable *cancellable) +{ + gs_free_error GError *error = NULL; + gs_free char *reason = NULL; + NetworkServerRegisterReqData *r_req_data; + + nm_utils_user_data_unpack (user_data, &r_req_data, &reason); + + nm_utils_error_set (&error, NM_UTILS_ERROR_UNKNOWN, + "registration was aborted due to %s", + reason); + _network_server_register_req_data_complete (r_req_data, error); +} + +static void +_network_server_unregister_bridge (NMBluezManager *self, + BzDBusObj *bzobj, + const char *reason) +{ + NMBluezManagerPrivate *priv = NM_BLUEZ_MANAGER_GET_PRIVATE (self); + _nm_unused gs_unref_object NMDevice *device = NULL; + NetworkServerRegisterReqData *r_req_data; + + nm_assert (NM_IS_DEVICE (bzobj->x_network_server.device_br)); + + _LOGD ("NAP: [%s]: unregistering \"%s\" (%s)", + bzobj->object_path, + nm_device_get_iface (bzobj->x_network_server.device_br), + reason); + + device = g_steal_pointer (&bzobj->x_network_server.device_br); + + r_req_data = g_steal_pointer (&bzobj->x_network_server.r_req_data); + + if (priv->name_owner) { + gs_unref_object GCancellable *cancellable = NULL; + + cancellable = g_cancellable_new (); - nm_clear_g_cancellable (&priv->async_cancellable); + nm_shutdown_wait_obj_register_cancellable_full (cancellable, + g_strdup_printf ("bt-unregister-nap[%s]", bzobj->object_path), + TRUE); - g_clear_object (&priv->introspect_proxy); + g_dbus_connection_call (priv->dbus_connection, + priv->name_owner, + bzobj->object_path, + NM_BLUEZ5_NETWORK_SERVER_INTERFACE, + "Unregister", + g_variant_new ("(s)", BLUETOOTH_CONNECT_NAP), + NULL, + G_DBUS_CALL_FLAGS_NO_AUTO_START, + -1, + cancellable, + _dbus_call_complete_cb_nop, + NULL); + } - if (do_unwatch_name && priv->watch_name_id) { - g_bus_unwatch_name (priv->watch_name_id); - priv->watch_name_id = 0; + if (r_req_data) { + nm_clear_g_cancellable (&r_req_data->int_cancellable); + nm_utils_invoke_on_idle (_network_server_unregister_bridge_complete_on_idle_cb, + nm_utils_user_data_pack (r_req_data, g_strdup (reason)), + r_req_data->ext_cancellable); } + + _nm_device_bridge_notify_unregister_bt_nap (device, reason); +} + +static gboolean +_network_server_vt_unregister_bridge (const NMBtVTableNetworkServer *vtable, + NMDevice *device) +{ + NMBluezManager *self = _network_server_get_bluez_manager (vtable); + NMBluezManagerPrivate *priv = NM_BLUEZ_MANAGER_GET_PRIVATE (self); + BzDBusObj *bzobj; + + g_return_val_if_fail (NM_IS_DEVICE (device), FALSE); + + bzobj = _network_server_find_has_device (priv, device); + if (bzobj) + _network_server_unregister_bridge (self, bzobj, "disconnecting"); + + return TRUE; } static void -manager_bdaddr_added_cb (GObject *manager, - NMBluezDevice *bt_device, - const char *bdaddr, - const char *name, - const char *object_path, - guint32 capabilities, - gpointer user_data) +_network_server_process_change (BzDBusObj *bzobj, + gboolean *out_emit_device_availability_changed) { - NMBluezManager *self = NM_BLUEZ_MANAGER (user_data); - NMDevice *device; - gboolean has_dun = (capabilities & NM_BT_CAPABILITY_DUN); - gboolean has_nap = (capabilities & NM_BT_CAPABILITY_NAP); + NMBluezManager *self = bzobj->self; + NMBluezManagerPrivate *priv = NM_BLUEZ_MANAGER_GET_PRIVATE (self); + gboolean network_server_is_usable; + gboolean emit_device_availability_changed = FALSE; + + network_server_is_usable = _bzobjs_network_server_is_usable (bzobj, TRUE); + + if (!network_server_is_usable) { + + if (!c_list_is_empty (&bzobj->x_network_server.lst)) { + emit_device_availability_changed = TRUE; + c_list_unlink (&bzobj->x_network_server.lst); + } + + nm_clear_g_free (&bzobj->x_network_server.adapter_address); + + if (bzobj->x_network_server.device_br) { + _network_server_unregister_bridge (self, + bzobj, + _bzobjs_network_server_is_usable (bzobj, FALSE) + ? "adapter disabled" + : "adapter disappeared"); + } + + } else { + + if (!nm_streq0 (bzobj->x_network_server.adapter_address, bzobj->d_adapter.address)) { + emit_device_availability_changed = TRUE; + g_free (bzobj->x_network_server.adapter_address); + bzobj->x_network_server.adapter_address = g_strdup (bzobj->d_adapter.address); + } - g_return_if_fail (bdaddr != NULL); - g_return_if_fail (name != NULL); - g_return_if_fail (object_path != NULL); - g_return_if_fail (capabilities != NM_BT_CAPABILITY_NONE); - g_return_if_fail (NM_IS_BLUEZ_DEVICE (bt_device)); + if (c_list_is_empty (&bzobj->x_network_server.lst)) { + emit_device_availability_changed = TRUE; + c_list_link_tail (&priv->network_server_lst_head, &bzobj->x_network_server.lst); + } - device = nm_device_bt_new (bt_device, object_path, bdaddr, name, capabilities); - if (!device) + } + + if (emit_device_availability_changed) + NM_SET_OUT (out_emit_device_availability_changed, TRUE); +} + +/*****************************************************************************/ + +static void +_conn_create_panu_connection (NMBluezManager *self, + BzDBusObj *bzobj) +{ + NMBluezManagerPrivate *priv = NM_BLUEZ_MANAGER_GET_PRIVATE (self); + gs_unref_object NMConnection *connection = NULL; + NMSettingsConnection *added; + NMSetting *setting; + gs_free char *id = NULL; + char uuid[37]; + gs_free_error GError *error = NULL; + + nm_utils_uuid_generate_buf (uuid); + id = g_strdup_printf (_("%s Network"), bzobj->d_device.name); + + connection = nm_simple_connection_new (); + + setting = nm_setting_connection_new (); + g_object_set (setting, + NM_SETTING_CONNECTION_ID, id, + NM_SETTING_CONNECTION_UUID, uuid, + NM_SETTING_CONNECTION_AUTOCONNECT, FALSE, + NM_SETTING_CONNECTION_TYPE, NM_SETTING_BLUETOOTH_SETTING_NAME, + NULL); + nm_connection_add_setting (connection, setting); + + setting = nm_setting_bluetooth_new (); + g_object_set (setting, + NM_SETTING_BLUETOOTH_BDADDR, bzobj->d_device.address, + NM_SETTING_BLUETOOTH_TYPE, NM_SETTING_BLUETOOTH_TYPE_PANU, + NULL); + nm_connection_add_setting (connection, setting); + + if (!nm_connection_normalize (connection, NULL, NULL, &error)) { + _LOGE ("connection: couldn't generate a connection for NAP device: %s", + error->message); + g_return_if_reached (); + } + + nm_assert (_conn_track_is_relevant_connection (connection, NULL, NULL)); + + _LOGT ("connection: create in-memory PANU connection %s (%s) for device \"%s\" (%s)", + uuid, + id, + bzobj->d_device.name, + bzobj->d_device.address); + + nm_settings_add_connection (priv->settings, + connection, + NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_ONLY, + NM_SETTINGS_CONNECTION_ADD_REASON_NONE, + NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED, + &added, + &error); + if (!added) { + _LOGW ("connection: couldn't add new Bluetooth connection for NAP device: '%s' (%s): %s", + id, uuid, error->message); return; + } + + if ( !_conn_track_is_relevant_for_sett_conn (added, NM_BT_CAPABILITY_NAP, bzobj->d_device.address) + || !_conn_track_find_elem (self, added) + || bzobj->x_device.panu_connection) { + _LOGE ("connection: something went wrong creating PANU connection %s (%s) for device '%s'", + uuid, id, bzobj->d_device.address); + g_return_if_reached (); + } + + bzobj->x_device.panu_connection = added; +} + +/*****************************************************************************/ + +static void +_device_state_changed_cb (NMDevice *device, + guint new_state_u, + guint old_state_u, + guint reason_u, + gpointer user_data) +{ + BzDBusObj *bzobj = user_data; - _LOGI ("BT device %s (%s) added (%s%s%s)", - name, - bdaddr, - has_dun ? "DUN" : "", - has_dun && has_nap ? " " : "", - has_nap ? "NAP" : ""); - g_signal_emit_by_name (self, NM_DEVICE_FACTORY_DEVICE_ADDED, device); - g_object_unref (device); + if (!_bzobjs_device_is_usable (bzobj, NULL, NULL)) { + /* the device got unusable? Need to revisit it... */ + _process_change_idle_schedule (bzobj->self, bzobj); + } } static void -manager_network_server_added_cb (GObject *manager, - gpointer user_data) +_device_process_change (BzDBusObj *bzobj) { - nm_device_factory_emit_component_added (NM_DEVICE_FACTORY (user_data), NULL); + NMBluezManager *self = bzobj->self; + gs_unref_object NMDeviceBt *device_added = NULL; + gs_unref_object NMDeviceBt *device_deleted = NULL; + gboolean device_is_usable; + gboolean create_panu_connection = FALSE; + + device_is_usable = _bzobjs_device_is_usable (bzobj, NULL, &create_panu_connection); + + if (create_panu_connection) { + bzobj->x_device_panu_connection_allow_create = FALSE; + _conn_create_panu_connection (self, bzobj); + device_is_usable = _bzobjs_device_is_usable (bzobj, NULL, NULL); + } else { + if ( device_is_usable + && bzobj->x_device_panu_connection_allow_create + && NM_FLAGS_HAS (bzobj->d_device_capabilities, NM_BT_CAPABILITY_NAP) + && _conn_track_find_head (self, NM_BT_CAPABILITY_NAP, bzobj->d_device.address) ) { + /* We have a useable device and also a panu-connection. We block future attemps + * to generate a connection. */ + bzobj->x_device_panu_connection_allow_create = FALSE; + } + if (bzobj->x_device.panu_connection) { + if (!NM_FLAGS_HAS (nm_settings_connection_get_flags (bzobj->x_device.panu_connection), + NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED)) { + /* the connection that we generated earlier still exists, but it's not longer the same + * as it was when we created it. Forget about it, so that we don't delete the profile later... */ + bzobj->x_device.panu_connection = NULL; + } else { + if ( !device_is_usable + || !_conn_track_is_relevant_for_sett_conn (bzobj->x_device.panu_connection, + NM_BT_CAPABILITY_NAP, + bzobj->d_device.address)) { + _LOGT ("connection: delete in-memory PANU connection %s (%s) as device %s", + nm_settings_connection_get_uuid (bzobj->x_device.panu_connection), + nm_settings_connection_get_id (bzobj->x_device.panu_connection), + !device_is_usable ? "is now unusable" : "no longer matches"); + bzobj->x_device_panu_connection_allow_create = TRUE; + nm_settings_connection_delete (g_steal_pointer (&bzobj->x_device.panu_connection), FALSE); + } + } + } + } + + bzobj->x_device_is_connected = device_is_usable + && _bzobjs_device_is_connected (bzobj); + + bzobj->x_device_is_usable = device_is_usable; + + if (bzobj->x_device.device_bt) { + const char *device_to_delete_msg; + + if (!device_is_usable) + device_to_delete_msg = "device became unusable"; + else if (!_nm_device_bt_for_same_device (bzobj->x_device.device_bt, + bzobj->object_path, + bzobj->d_device.address, + NULL, + bzobj->d_device_capabilities)) + device_to_delete_msg = "device is no longer compatible"; + else + device_to_delete_msg = NULL; + + if (device_to_delete_msg) { + nm_clear_g_signal_handler (bzobj->x_device.device_bt, &bzobj->x_device.device_bt_signal_id); + + device_deleted = g_steal_pointer (&bzobj->x_device.device_bt); + + _LOGD ("[%s]: drop device because %s", + bzobj->object_path, + device_to_delete_msg); + + _connect_disconnect (self, bzobj, device_to_delete_msg); + } + } + + if (device_is_usable) { + if (!bzobj->x_device.device_bt) { + bzobj->x_device.device_bt = nm_device_bt_new (self, + bzobj->object_path, + bzobj->d_device.address, + bzobj->d_device.name, + bzobj->d_device_capabilities); + device_added = g_object_ref (bzobj->x_device.device_bt); + bzobj->x_device.device_bt_signal_id = g_signal_connect (device_added, + NM_DEVICE_STATE_CHANGED, + G_CALLBACK (_device_state_changed_cb), + bzobj); + } else + _nm_device_bt_notify_set_name (bzobj->x_device.device_bt, bzobj->d_device.name); + + _nm_device_bt_notify_set_connected (bzobj->x_device.device_bt, bzobj->x_device_is_connected); + } + + if ( bzobj->x_device.c_req_data + && !bzobj->x_device.c_req_data->int_cancellable + && bzobj->x_device_is_connected) { + gs_free char *device_name = g_steal_pointer (&bzobj->x_device.c_req_data->device_name); + + _device_connect_req_data_complete (g_steal_pointer (&bzobj->x_device.c_req_data), + self, + device_name, + NULL); + } + + if (device_added) + g_signal_emit_by_name (self, NM_DEVICE_FACTORY_DEVICE_ADDED, device_added); + + if (device_deleted) + _nm_device_bt_notify_removed (device_deleted); } +/*****************************************************************************/ + static void -setup_bluez5 (NMBluezManager *self) +_process_change_idle_all (NMBluezManager *self, + gboolean *out_emit_device_availability_changed) { - NMBluez5Manager *manager; NMBluezManagerPrivate *priv = NM_BLUEZ_MANAGER_GET_PRIVATE (self); + BzDBusObj *bzobj; + + while ((bzobj = c_list_first_entry (&priv->process_change_lst_head, BzDBusObj, process_change_lst))) { + + c_list_unlink (&bzobj->process_change_lst); - g_return_if_fail (!priv->manager5); + _LOG_bzobj (bzobj, "before-processing"); - cleanup_checking (self, TRUE); + _device_process_change (bzobj); - priv->manager5 = manager = nm_bluez5_manager_new (priv->settings); + _network_server_process_change (bzobj, out_emit_device_availability_changed); - g_signal_connect (manager, - NM_BLUEZ_MANAGER_BDADDR_ADDED, - G_CALLBACK (manager_bdaddr_added_cb), - self); - g_signal_connect (manager, - NM_BLUEZ_MANAGER_NETWORK_SERVER_ADDED, - G_CALLBACK (manager_network_server_added_cb), - self); + _LOG_bzobj (bzobj, "after-processing"); - nm_bluez5_manager_query_devices (manager); + _bzobjs_del_if_dead (bzobj); + } + + nm_clear_g_source (&priv->process_change_idle_id); +} + +static gboolean +_process_change_idle_cb (gpointer user_data) +{ + NMBluezManager *self = user_data; + NMBluezManagerPrivate *priv = NM_BLUEZ_MANAGER_GET_PRIVATE (self); + gboolean emit_device_availability_changed = FALSE; + + _process_change_idle_all (self, &emit_device_availability_changed); + + if (emit_device_availability_changed) + nm_manager_notify_device_availibility_maybe_changed (priv->manager); + + return G_SOURCE_CONTINUE; } static void -watch_name_on_appeared (GDBusConnection *connection, - const char *name, - const char *name_owner, - gpointer user_data) +_process_change_idle_schedule (NMBluezManager *self, + BzDBusObj *bzobj) { - check_bluez_and_try_setup (NM_BLUEZ_MANAGER (user_data)); + NMBluezManagerPrivate *priv = NM_BLUEZ_MANAGER_GET_PRIVATE (self); + + nm_c_list_move_tail (&priv->process_change_lst_head, &bzobj->process_change_lst); + if (priv->process_change_idle_id == 0) + priv->process_change_idle_id = g_idle_add_full (G_PRIORITY_DEFAULT_IDLE + 1, _process_change_idle_cb, self, NULL); } static void -check_bluez_and_try_setup_final_step (NMBluezManager *self, gboolean ready, const char *reason) +_dbus_process_changes (NMBluezManager *self, + BzDBusObj *bzobj, + const char *log_reason) { NMBluezManagerPrivate *priv = NM_BLUEZ_MANAGER_GET_PRIVATE (self); + gboolean network_server_is_usable; + gboolean adapter_is_usable_for_device; + gboolean device_is_usable; + gboolean changes = FALSE; + gboolean recheck_devices_for_adapter = FALSE; + + nm_assert (bzobj); + + _LOG_bzobj (bzobj, log_reason); + + device_is_usable = _bzobjs_device_is_usable (bzobj, NULL, NULL); + + if (bzobj->x_device_is_usable != device_is_usable) + changes = TRUE; + else if (bzobj->x_device.device_bt) { + if (!device_is_usable) + changes = TRUE; + else { + if ( bzobj->x_device_is_connected != _bzobjs_device_is_connected (bzobj) + || !_nm_device_bt_for_same_device (bzobj->x_device.device_bt, + bzobj->object_path, + bzobj->d_device.address, + bzobj->d_device.name, + bzobj->d_device_capabilities)) + changes = TRUE; + } + } - if (ready) { - setup_bluez5 (self); + adapter_is_usable_for_device = _bzobjs_adapter_is_usable_for_device (bzobj); + if (adapter_is_usable_for_device != bzobj->was_usable_adapter_for_device_before) { + /* this function does not modify bzobj in any other cases except here. + * Usually changes are processed delayed, in the idle handler. + * + * But the bzobj->was_usable_adapter_for_device_before only exists to know whether + * we need to re-check device availability. It is correct to set the flag + * here, right before we checked. */ + bzobj->was_usable_adapter_for_device_before = adapter_is_usable_for_device; + recheck_devices_for_adapter = TRUE; + changes = TRUE; + } + + if (!changes) { + network_server_is_usable = _bzobjs_network_server_is_usable (bzobj, TRUE); + + if (network_server_is_usable != (!c_list_is_empty (&bzobj->x_network_server.lst))) + changes = TRUE; + else if ( bzobj->x_network_server.device_br + && !network_server_is_usable) + changes = TRUE; + else if (!nm_streq0 (bzobj->d_has_adapter_iface ? bzobj->d_adapter.address : NULL, + bzobj->x_network_server.adapter_address)) + changes = TRUE; + } + + if (changes) + _process_change_idle_schedule (self, bzobj); + + if (recheck_devices_for_adapter) { + GHashTableIter iter; + BzDBusObj *bzobj2; + + /* we got a change to the availability of an adapter. We might need to recheck + * all devices that use this adapter... */ + g_hash_table_iter_init (&iter, priv->bzobjs); + while (g_hash_table_iter_next (&iter, (gpointer *) &bzobj2, NULL)) { + if (bzobj2 == bzobj) + continue; + if (!nm_streq0 (bzobj2->d_device.adapter, bzobj->object_path)) + continue; + if (c_list_is_empty (&bzobj2->process_change_lst)) + _dbus_process_changes (self, bzobj2, "adapter-changed"); + else + nm_c_list_move_tail (&priv->process_change_lst_head, &bzobj2->process_change_lst); + } + } + + _bzobjs_del_if_dead (bzobj); +} + +/*****************************************************************************/ + +#define ALL_RELEVANT_INTERFACE_NAMES NM_MAKE_STRV (NM_BLUEZ5_ADAPTER_INTERFACE, \ + NM_BLUEZ5_DEVICE_INTERFACE, \ + NM_BLUEZ5_NETWORK_INTERFACE, \ + NM_BLUEZ5_NETWORK_SERVER_INTERFACE) + +static gboolean +_dbus_handle_properties_changed (NMBluezManager *self, + const char *object_path, + const char *interface_name, + GVariant *changed_properties, + const char *const*invalidated_properties, + BzDBusObj **inout_bzobj) +{ + BzDBusObj *bzobj = NULL; + gboolean changed = FALSE; + const char *property_name; + GVariant *property_value; + GVariantIter iter_prop; + gsize i; + + if (!invalidated_properties) + invalidated_properties = NM_PTRARRAY_EMPTY (const char *); + + nm_assert (g_variant_is_of_type (changed_properties, G_VARIANT_TYPE ("a{sv}"))); + + if (inout_bzobj) { + bzobj = *inout_bzobj; + nm_assert (!bzobj || nm_streq (object_path, bzobj->object_path)); + } + + if (changed_properties) + g_variant_iter_init (&iter_prop, changed_properties); + + if (nm_streq (interface_name, NM_BLUEZ5_ADAPTER_INTERFACE)) { + _bzobjs_init (self, &bzobj, object_path); + if (!bzobj->d_has_adapter_iface) { + changed = TRUE; + bzobj->d_has_adapter_iface = TRUE; + } + + while ( changed_properties + && g_variant_iter_next (&iter_prop, "{&sv}", &property_name, &property_value)) { + _nm_unused gs_unref_variant GVariant *property_value_free = property_value; + + if (nm_streq (property_name, "Address")) { + gs_free char *s = g_variant_is_of_type (property_value, G_VARIANT_TYPE_STRING) + ? nm_utils_hwaddr_canonical (g_variant_get_string (property_value, NULL), ETH_ALEN) + : NULL; + + if (!nm_streq0 (bzobj->d_adapter.address, s)) { + changed = TRUE; + nm_clear_g_free (&bzobj->d_adapter.address); + bzobj->d_adapter.address = g_steal_pointer (&s); + } + continue; + } + if (nm_streq (property_name, "Powered")) { + bool v = g_variant_is_of_type (property_value, G_VARIANT_TYPE_BOOLEAN) + && g_variant_get_boolean (property_value); + + if (bzobj->d_adapter_powered != v) { + changed = TRUE; + bzobj->d_adapter_powered = v; + } + continue; + } + } + + for (i = 0; (property_name = invalidated_properties[i]); i++) { + if (nm_streq (property_name, "Address")) { + if (bzobj->d_adapter.address) { + changed = TRUE; + nm_clear_g_free (&bzobj->d_adapter.address); + } + continue; + } + if (nm_streq (property_name, "Powered")) { + if (bzobj->d_adapter_powered) { + changed = TRUE; + bzobj->d_adapter_powered = FALSE; + } + continue; + } + } + + } else if (nm_streq (interface_name, NM_BLUEZ5_DEVICE_INTERFACE)) { + _bzobjs_init (self, &bzobj, object_path); + if (!bzobj->d_has_device_iface) { + changed = TRUE; + bzobj->d_has_device_iface = TRUE; + } + + while ( changed_properties + && g_variant_iter_next (&iter_prop, "{&sv}", &property_name, &property_value)) { + _nm_unused gs_unref_variant GVariant *property_value_free = property_value; + + if (nm_streq (property_name, "Address")) { + gs_free char *s = g_variant_is_of_type (property_value, G_VARIANT_TYPE_STRING) + ? nm_utils_hwaddr_canonical (g_variant_get_string (property_value, NULL), ETH_ALEN) + : NULL; + + if (!nm_streq0 (bzobj->d_device.address, s)) { + changed = TRUE; + nm_clear_g_free (&bzobj->d_device.address); + bzobj->d_device.address = g_steal_pointer (&s); + } + continue; + } + if (nm_streq (property_name, "Name")) { + const char *s = g_variant_is_of_type (property_value, G_VARIANT_TYPE_STRING) + ? g_variant_get_string (property_value, NULL) + : NULL; + + if (!nm_streq0 (bzobj->d_device.name, s)) { + changed = TRUE; + nm_clear_g_free (&bzobj->d_device.name); + bzobj->d_device.name = g_strdup (s); + } + continue; + } + if (nm_streq (property_name, "Adapter")) { + const char *s = g_variant_is_of_type (property_value, G_VARIANT_TYPE_OBJECT_PATH) + ? g_variant_get_string (property_value, NULL) + : NULL; + + if (!nm_streq0 (bzobj->d_device.adapter, s)) { + changed = TRUE; + nm_clear_g_free (&bzobj->d_device.adapter); + bzobj->d_device.adapter = g_strdup (s); + } + continue; + } + if (nm_streq (property_name, "UUIDs")) { + NMBluetoothCapabilities capabilities = NM_BT_CAPABILITY_NONE; + + if (g_variant_is_of_type (property_value, G_VARIANT_TYPE_STRING_ARRAY)) { + gs_free const char **s = g_variant_get_strv (property_value, NULL); + + capabilities = convert_uuids_to_capabilities (s); + } + if (bzobj->d_device_capabilities != capabilities) { + changed = TRUE; + bzobj->d_device_capabilities = capabilities; + nm_assert (bzobj->d_device_capabilities == capabilities); + } + continue; + } + if (nm_streq (property_name, "Connected")) { + bool v = g_variant_is_of_type (property_value, G_VARIANT_TYPE_BOOLEAN) + && g_variant_get_boolean (property_value); + + if (bzobj->d_device_connected != v) { + changed = TRUE; + bzobj->d_device_connected = v; + } + continue; + } + if (nm_streq (property_name, "Paired")) { + bool v = g_variant_is_of_type (property_value, G_VARIANT_TYPE_BOOLEAN) + && g_variant_get_boolean (property_value); + + if (bzobj->d_device_paired != v) { + changed = TRUE; + bzobj->d_device_paired = v; + } + continue; + } + } + + for (i = 0; (property_name = invalidated_properties[i]); i++) { + if (nm_streq (property_name, "Address")) { + if (bzobj->d_device.address) { + changed = TRUE; + nm_clear_g_free (&bzobj->d_device.address); + } + continue; + } + if (nm_streq (property_name, "Name")) { + if (bzobj->d_device.name) { + changed = TRUE; + nm_clear_g_free (&bzobj->d_device.name); + } + continue; + } + if (nm_streq (property_name, "Adapter")) { + if (bzobj->d_device.adapter) { + changed = TRUE; + nm_clear_g_free (&bzobj->d_device.adapter); + } + continue; + } + if (nm_streq (property_name, "UUIDs")) { + if (bzobj->d_device_capabilities != NM_BT_CAPABILITY_NONE) { + changed = TRUE; + bzobj->d_device_capabilities = NM_BT_CAPABILITY_NONE; + } + continue; + } + if (nm_streq (property_name, "Connected")) { + if (bzobj->d_device_connected) { + changed = TRUE; + bzobj->d_device_connected = FALSE; + } + continue; + } + if (nm_streq (property_name, "Paired")) { + if (bzobj->d_device_paired) { + changed = TRUE; + bzobj->d_device_paired = FALSE; + } + continue; + } + } + + } else if (nm_streq (interface_name, NM_BLUEZ5_NETWORK_INTERFACE)) { + _bzobjs_init (self, &bzobj, object_path); + if (!bzobj->d_has_network_iface) { + changed = TRUE; + bzobj->d_has_network_iface = TRUE; + } + + while ( changed_properties + && g_variant_iter_next (&iter_prop, "{&sv}", &property_name, &property_value)) { + _nm_unused gs_unref_variant GVariant *property_value_free = property_value; + + if (nm_streq (property_name, "Interface")) { + const char *s = g_variant_is_of_type (property_value, G_VARIANT_TYPE_STRING) + ? g_variant_get_string (property_value, NULL) + : NULL; + + if (!nm_streq0 (bzobj->d_network.interface, s)) { + changed = TRUE; + nm_clear_g_free (&bzobj->d_network.interface); + bzobj->d_network.interface = g_strdup (s); + } + continue; + } + if (nm_streq (property_name, "Connected")) { + bool v = g_variant_is_of_type (property_value, G_VARIANT_TYPE_BOOLEAN) + && g_variant_get_boolean (property_value); + + if (bzobj->d_network_connected != v) { + changed = TRUE; + bzobj->d_network_connected = v; + } + continue; + } + } + + for (i = 0; (property_name = invalidated_properties[i]); i++) { + if (nm_streq (property_name, "Interface")) { + if (bzobj->d_network.interface) { + changed = TRUE; + nm_clear_g_free (&bzobj->d_network.interface); + } + continue; + } + if (nm_streq (property_name, "Connected")) { + if (bzobj->d_network_connected) { + changed = TRUE; + bzobj->d_network_connected = FALSE; + } + continue; + } + } + + } else if (nm_streq (interface_name, NM_BLUEZ5_NETWORK_SERVER_INTERFACE)) { + _bzobjs_init (self, &bzobj, object_path); + if (!bzobj->d_has_network_server_iface) { + changed = TRUE; + bzobj->d_has_network_server_iface = TRUE; + } + } + + nm_assert (!changed || bzobj); + + if (inout_bzobj) + *inout_bzobj = bzobj; + + return changed; +} + +static void +_dbus_handle_interface_added (NMBluezManager *self, + const char *object_path, + GVariant *ifaces, + gboolean initial_get_managed_objects) +{ + BzDBusObj *bzobj = NULL; + gboolean changed = FALSE; + const char *interface_name; + GVariant *changed_properties; + GVariantIter iter_ifaces; + + nm_assert (g_variant_is_of_type (ifaces, G_VARIANT_TYPE ("a{sa{sv}}"))); + + g_variant_iter_init (&iter_ifaces, ifaces); + while (g_variant_iter_next (&iter_ifaces, "{&s@a{sv}}", &interface_name, &changed_properties)) { + _nm_unused gs_unref_variant GVariant *changed_properties_free = changed_properties; + + if (_dbus_handle_properties_changed (self, object_path, interface_name, changed_properties, NULL, &bzobj)) + changed = TRUE; + } + + if (changed) { + _dbus_process_changes (self, + bzobj, + initial_get_managed_objects + ? "dbus-init" + : "dbus-iface-added"); + } +} + +static gboolean +_dbus_handle_interface_removed (NMBluezManager *self, + const char *object_path, + BzDBusObj **inout_bzobj, + const char *const*removed_interfaces) +{ + gboolean changed = FALSE; + BzDBusObj *bzobj; + gsize i; + + if ( inout_bzobj + && *inout_bzobj) { + bzobj = *inout_bzobj; + nm_assert (bzobj == _bzobjs_get (self, object_path)); + } else { + bzobj = _bzobjs_get (self, object_path); + if (!bzobj) + return FALSE; + NM_SET_OUT (inout_bzobj, bzobj); + } + + for (i = 0; removed_interfaces[i]; i++) { + const char *interface_name = removed_interfaces[i]; + + if (nm_streq (interface_name, NM_BLUEZ5_ADAPTER_INTERFACE)) { + if (bzobj->d_has_adapter_iface) { + changed = TRUE; + bzobj->d_has_adapter_iface = FALSE; + } + if (bzobj->d_adapter.address) { + changed = TRUE; + nm_clear_g_free (&bzobj->d_adapter.address); + } + if (bzobj->d_adapter_powered) { + changed = TRUE; + bzobj->d_adapter_powered = FALSE; + } + } else if (nm_streq (interface_name, NM_BLUEZ5_DEVICE_INTERFACE)) { + if (bzobj->d_has_device_iface) { + changed = TRUE; + bzobj->d_has_device_iface = FALSE; + } + if (bzobj->d_device.address) { + changed = TRUE; + nm_clear_g_free (&bzobj->d_device.address); + } + if (bzobj->d_device.name) { + changed = TRUE; + nm_clear_g_free (&bzobj->d_device.name); + } + if (bzobj->d_device.adapter) { + changed = TRUE; + nm_clear_g_free (&bzobj->d_device.adapter); + } + if (bzobj->d_device_capabilities != NM_BT_CAPABILITY_NONE) { + changed = TRUE; + bzobj->d_device_capabilities = NM_BT_CAPABILITY_NONE; + } + if (bzobj->d_device_connected) { + changed = TRUE; + bzobj->d_device_connected = FALSE; + } + if (bzobj->d_device_paired) { + changed = TRUE; + bzobj->d_device_paired = FALSE; + } + } else if (nm_streq (interface_name, NM_BLUEZ5_NETWORK_INTERFACE)) { + if (bzobj->d_has_network_iface) { + changed = TRUE; + bzobj->d_has_network_iface = FALSE; + } + if (bzobj->d_network.interface) { + changed = TRUE; + nm_clear_g_free (&bzobj->d_network.interface); + } + if (bzobj->d_network_connected) { + changed = TRUE; + bzobj->d_network_connected = FALSE; + } + } else if (nm_streq (interface_name, NM_BLUEZ5_NETWORK_SERVER_INTERFACE)) { + if (bzobj->d_has_network_server_iface) { + changed = TRUE; + bzobj->d_has_network_server_iface = FALSE; + } + } + } + + return changed; +} + +static void +_dbus_managed_objects_changed_cb (const char *object_path, + GVariant *added_interfaces_and_properties, + const char *const*removed_interfaces, + gpointer user_data) +{ + NMBluezManager *self = user_data; + NMBluezManagerPrivate *priv = NM_BLUEZ_MANAGER_GET_PRIVATE (self); + BzDBusObj *bzobj = NULL; + gboolean changed; + + if (priv->get_managed_objects_cancellable) { + /* we still wait for the initial GetManagedObjects(). Ignore the event. */ return; } - _LOGD ("detecting BlueZ version failed: %s", reason); + if (!added_interfaces_and_properties) { + changed = _dbus_handle_interface_removed (self, object_path, &bzobj, removed_interfaces); + if (changed) + _dbus_process_changes (self, bzobj, "dbus-iface-removed"); + } else + _dbus_handle_interface_added (self, object_path, added_interfaces_and_properties, FALSE); +} - /* cancel current attempts to detect the version. */ - cleanup_checking (self, FALSE); - if (!priv->watch_name_id) { - priv->watch_name_id = g_bus_watch_name (G_BUS_TYPE_SYSTEM, - NM_BLUEZ_SERVICE, - G_BUS_NAME_WATCHER_FLAGS_NONE, - watch_name_on_appeared, - NULL, - self, - NULL); +static void +_dbus_properties_changed_cb (GDBusConnection *connection, + const char *sender_name, + const char *object_path, + const char *signal_interface_name, + const char *signal_name, + GVariant *parameters, + gpointer user_data) +{ + NMBluezManager *self = user_data; + NMBluezManagerPrivate *priv = NM_BLUEZ_MANAGER_GET_PRIVATE (self); + const char *interface_name; + gs_unref_variant GVariant *changed_properties = NULL; + gs_free const char **invalidated_properties = NULL; + BzDBusObj *bzobj = NULL; + + if (priv->get_managed_objects_cancellable) { + /* we still wait for the initial GetManagedObjects(). Ignore the event. */ + return; } + + if (!g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(sa{sv}as)"))) + return; + + g_variant_get (parameters, + "(&s@a{sv}^a&s)", + &interface_name, + &changed_properties, + &invalidated_properties); + + if (_dbus_handle_properties_changed (self, object_path, interface_name, changed_properties, invalidated_properties, &bzobj)) + _dbus_process_changes (self, bzobj, "dbus-property-changed"); } static void -check_bluez_and_try_setup_do_introspect (GObject *source_object, - GAsyncResult *res, - gpointer user_data) +_dbus_get_managed_objects_cb (GVariant *result, + GError *error, + gpointer user_data) { - NMBluezManager *self = async_data_unpack (user_data); + NMBluezManager *self; NMBluezManagerPrivate *priv; - GError *error = NULL; - gs_unref_variant GVariant *result = NULL; - const char *reason = NULL; + GVariantIter iter; + const char *object_path; + GVariant *ifaces; - if (!self) + if ( !result + && nm_utils_error_is_cancelled (error, FALSE)) return; + self = user_data; priv = NM_BLUEZ_MANAGER_GET_PRIVATE (self); - g_return_if_fail (priv->introspect_proxy); - g_return_if_fail (!g_cancellable_is_cancelled (priv->async_cancellable)); - - g_clear_object (&priv->async_cancellable); + g_clear_object (&priv->get_managed_objects_cancellable); - result = _nm_dbus_proxy_call_finish (priv->introspect_proxy, res, - G_VARIANT_TYPE ("(s)"), &error); if (!result) { - char *reason2; - - g_dbus_error_strip_remote_error (error); - reason2 = g_strdup_printf ("introspect failed with %s", error->message); - check_bluez_and_try_setup_final_step (self, FALSE, reason2); - g_error_free (error); - g_free (reason2); + _LOGT ("initial GetManagedObjects() call failed: %s", error->message); + _cleanup_for_name_owner (self); return; } - check_bluez_and_try_setup_final_step (self, TRUE, reason); + _LOGT ("initial GetManagedObjects call succeeded"); + + g_variant_iter_init (&iter, result); + while (g_variant_iter_next (&iter, "{&o@a{sa{sv}}}", &object_path, &ifaces)) { + _nm_unused gs_unref_variant GVariant *ifaces_free = ifaces; + + _dbus_handle_interface_added (self, object_path, ifaces, TRUE); + } } +/*****************************************************************************/ + static void -check_bluez_and_try_setup_on_new_proxy (GObject *source_object, - GAsyncResult *res, - gpointer user_data) +_cleanup_for_name_owner (NMBluezManager *self) { - NMBluezManager *self = async_data_unpack (user_data); - NMBluezManagerPrivate *priv; - GError *error = NULL; + NMBluezManagerPrivate *priv = NM_BLUEZ_MANAGER_GET_PRIVATE (self); + gboolean emit_device_availability_changed = FALSE; + GHashTableIter iter; + BzDBusObj *bzobj; + gboolean first = TRUE; + + nm_clear_g_cancellable (&priv->get_managed_objects_cancellable); + + nm_clear_g_dbus_connection_signal (priv->dbus_connection, + &priv->managed_objects_changed_id); + nm_clear_g_dbus_connection_signal (priv->dbus_connection, + &priv->properties_changed_id); + + nm_clear_g_free (&priv->name_owner); + + g_hash_table_iter_init (&iter, priv->bzobjs); + while (g_hash_table_iter_next (&iter, (gpointer *) &bzobj, NULL)) { + if (first) { + first = FALSE; + _LOGT ("drop all objects form D-Bus cache..."); + } + _dbus_handle_interface_removed (self, + bzobj->object_path, + &bzobj, + ALL_RELEVANT_INTERFACE_NAMES); + nm_c_list_move_tail (&priv->process_change_lst_head, &bzobj->process_change_lst); + } + _process_change_idle_all (self, &emit_device_availability_changed); + nm_assert (g_hash_table_size (priv->bzobjs) == 0); + + if (emit_device_availability_changed) + nm_manager_notify_device_availibility_maybe_changed (priv->manager); +} + +static void +name_owner_changed (NMBluezManager *self, + const char *owner) +{ + _nm_unused gs_unref_object NMBluezManager *self_keep_alive = g_object_ref (self); + NMBluezManagerPrivate *priv = NM_BLUEZ_MANAGER_GET_PRIVATE (self); + + owner = nm_str_not_empty (owner); + + if (!owner) + _LOGT ("D-Bus name for bluez has no owner"); + else + _LOGT ("D-Bus name for bluez has owner %s", owner); + + nm_clear_g_cancellable (&priv->name_owner_get_cancellable); - if (!self) + if (nm_streq0 (priv->name_owner, owner)) return; - priv = NM_BLUEZ_MANAGER_GET_PRIVATE (self); + _cleanup_for_name_owner (self); - g_return_if_fail (!priv->introspect_proxy); - g_return_if_fail (!g_cancellable_is_cancelled (priv->async_cancellable)); + if (!owner) + return; - priv->introspect_proxy = g_dbus_proxy_new_for_bus_finish (res, &error); + priv->name_owner = g_strdup (owner); + + priv->get_managed_objects_cancellable = g_cancellable_new (); + + priv->managed_objects_changed_id = nm_dbus_connection_signal_subscribe_object_manager (priv->dbus_connection, + priv->name_owner, + NM_BLUEZ_MANAGER_PATH, + _dbus_managed_objects_changed_cb, + self, + NULL); + + priv->properties_changed_id = nm_dbus_connection_signal_subscribe_properties_changed (priv->dbus_connection, + priv->name_owner, + NULL, + NULL, + _dbus_properties_changed_cb, + self, + NULL); + + nm_dbus_connection_call_get_managed_objects (priv->dbus_connection, + priv->name_owner, + NM_BLUEZ_MANAGER_PATH, + G_DBUS_CALL_FLAGS_NO_AUTO_START, + 20000, + priv->get_managed_objects_cancellable, + _dbus_get_managed_objects_cb, + self); +} - if (!priv->introspect_proxy) { - char *reason = g_strdup_printf ("bluez error creating dbus proxy: %s", error->message); +static void +name_owner_changed_cb (GDBusConnection *connection, + const char *sender_name, + const char *object_path, + const char *interface_name, + const char *signal_name, + GVariant *parameters, + gpointer user_data) +{ + NMBluezManager *self = user_data; + const char *new_owner; - check_bluez_and_try_setup_final_step (self, FALSE, reason); - g_error_free (error); - g_free (reason); + if (!g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(sss)"))) return; - } - g_dbus_proxy_call (priv->introspect_proxy, - "Introspect", - NULL, - G_DBUS_CALL_FLAGS_NO_AUTO_START, - 3000, - priv->async_cancellable, - check_bluez_and_try_setup_do_introspect, - async_data_pack (self)); + g_variant_get (parameters, + "(&s&s&s)", + NULL, + NULL, + &new_owner); + + name_owner_changed (self, new_owner); } static void -check_bluez_and_try_setup (NMBluezManager *self) +name_owner_get_cb (const char *name_owner, + GError *error, + gpointer user_data) +{ + if ( name_owner + || !g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + name_owner_changed (user_data, name_owner); +} + +/*****************************************************************************/ + +static void +_cleanup_all (NMBluezManager *self) { NMBluezManagerPrivate *priv = NM_BLUEZ_MANAGER_GET_PRIVATE (self); - /* there should be no ongoing detection. Anyway, cleanup_checking. */ - cleanup_checking (self, FALSE); + priv->settings_registered = FALSE; + + g_signal_handlers_disconnect_by_func (priv->settings, cp_connection_added, self); + g_signal_handlers_disconnect_by_func (priv->settings, cp_connection_updated, self); + g_signal_handlers_disconnect_by_func (priv->settings, cp_connection_removed, self); + + g_hash_table_remove_all (priv->conn_data_elems); + g_hash_table_remove_all (priv->conn_data_heads); - priv->async_cancellable = g_cancellable_new (); + _cleanup_for_name_owner (self); - g_dbus_proxy_new_for_bus (G_BUS_TYPE_SYSTEM, - G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES | G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START, - NULL, - NM_BLUEZ_SERVICE, - "/", - DBUS_INTERFACE_INTROSPECTABLE, - priv->async_cancellable, - check_bluez_and_try_setup_on_new_proxy, - async_data_pack (self)); + nm_clear_g_cancellable (&priv->name_owner_get_cancellable); + + nm_clear_g_dbus_connection_signal (priv->dbus_connection, + &priv->name_owner_changed_id); } static void start (NMDeviceFactory *factory) { - check_bluez_and_try_setup (NM_BLUEZ_MANAGER (factory)); + NMBluezManager *self; + NMBluezManagerPrivate *priv; + NMSettingsConnection *const*sett_conns; + guint n_sett_conns; + guint i; + + g_return_if_fail (NM_IS_BLUEZ_MANAGER (factory)); + + self = NM_BLUEZ_MANAGER (factory); + priv = NM_BLUEZ_MANAGER_GET_PRIVATE (self); + + _cleanup_all (self); + + if (!priv->dbus_connection) { + _LOGI ("no D-Bus connection available"); + return; + } + + g_signal_connect (priv->settings, NM_SETTINGS_SIGNAL_CONNECTION_ADDED, G_CALLBACK (cp_connection_added), self); + g_signal_connect (priv->settings, NM_SETTINGS_SIGNAL_CONNECTION_UPDATED, G_CALLBACK (cp_connection_updated), self); + g_signal_connect (priv->settings, NM_SETTINGS_SIGNAL_CONNECTION_REMOVED, G_CALLBACK (cp_connection_removed), self); + + priv->settings_registered = TRUE; + + sett_conns = nm_settings_get_connections (priv->settings, &n_sett_conns); + for (i = 0; i < n_sett_conns; i++) + _conn_track_update (self, sett_conns[i], TRUE, NULL, NULL, NULL); + + priv->name_owner_changed_id = nm_dbus_connection_signal_subscribe_name_owner_changed (priv->dbus_connection, + NM_BLUEZ_SERVICE, + name_owner_changed_cb, + self, + NULL); + + priv->name_owner_get_cancellable = g_cancellable_new (); + + nm_dbus_connection_call_get_name_owner (priv->dbus_connection, + NM_BLUEZ_SERVICE, + 10000, + priv->name_owner_get_cancellable, + name_owner_get_cb, + self); +} + +/*****************************************************************************/ + +static void +_connect_returned (NMBluezManager *self, + BzDBusObj *bzobj, + NMBluetoothCapabilities bt_type, + const char *device_name, + NMBluez5DunContext *dun_context, + GError *error) +{ + char sbuf_cap[100]; + + if (error) { + nm_assert (!device_name); + nm_assert (!dun_context); + + _LOGI ("%s [%s]: connect failed: %s", + nm_bluetooth_capability_to_string (bzobj->x_device_connect_bt_type, sbuf_cap, sizeof (sbuf_cap)), + bzobj->object_path, + error->message); + + _device_connect_req_data_complete (g_steal_pointer (&bzobj->x_device.c_req_data), + self, + NULL, + error); + _connect_disconnect (self, bzobj, "cleanup after connect failure"); + return; + } + + nm_assert (bzobj->x_device_connect_bt_type == bt_type); + nm_assert (device_name); + nm_assert ((bt_type == NM_BT_CAPABILITY_DUN) == (!!dun_context)); + nm_assert (bzobj->x_device.c_req_data); + + g_clear_object (&bzobj->x_device.c_req_data->int_cancellable); + + bzobj->x_device.connect_dun_context = dun_context; + + _LOGD ("%s [%s]: connect successful to device %s", + nm_bluetooth_capability_to_string (bzobj->x_device_connect_bt_type, sbuf_cap, sizeof (sbuf_cap)), + bzobj->object_path, + device_name); + + /* we already have another over-all timer running. But after we connected the device, + * we still need to wait for bluez to acknowledge the connected state (via D-Bus, for NAP). + * For DUN profiles we likely are already fully connected by now. + * + * Anyway, schedule another timeout that is possibly shorter than the overall, original + * timeout. Now this should go down fast. */ + bzobj->x_device.c_req_data->timeout_wait_connect_id = g_timeout_add (5000, + _connect_timeout_wait_connected_cb, + bzobj), + bzobj->x_device.c_req_data->device_name = g_strdup (device_name); + + if ( _bzobjs_device_is_usable (bzobj, NULL, NULL) + && _bzobjs_device_is_connected (bzobj)) { + /* We are now connected. Schedule the task that completes the state. */ + _process_change_idle_schedule (self, bzobj); + } +} + +#if WITH_BLUEZ5_DUN +static void +_connect_dun_notify_tty_hangup_cb (NMBluez5DunContext *context, + gpointer user_data) +{ + BzDBusObj *bzobj = user_data; + + _connect_disconnect (bzobj->self, + bzobj, + "DUN connection hung up"); +} + +static void +_connect_dun_step2_cb (NMBluez5DunContext *context, + const char *rfcomm_dev, + GError *error, + gpointer user_data) +{ + BzDBusObj *bzobj; + + if (nm_utils_error_is_cancelled (error, FALSE)) + return; + + bzobj = user_data; + + if (rfcomm_dev) { + /* We want to early notifiy about the rfcomm path. That is because we might still delay + * to signal full activation longer (asynchronously). But the earliest time the callback + * is invoked with the rfcomm path, we just created the device synchronously. + * + * By already notifying the caller about the path early, it avoids a race where ModemManager + * would find the modem before the bluetooth code considers the profile fully activated. */ + + nm_assert (!error); + nm_assert (bzobj->x_device.c_req_data); + + if (!g_cancellable_is_cancelled (bzobj->x_device.c_req_data->ext_cancellable)) + bzobj->x_device.c_req_data->callback (bzobj->self, FALSE, rfcomm_dev, NULL, bzobj->x_device.c_req_data->callback_user_data); + + if (!context) { + /* No context set. This means, we just got notified about the rfcomm path and need to wait + * longer, for the next callback. */ + return; + } + } + + _connect_returned (bzobj->self, bzobj, NM_BT_CAPABILITY_DUN, rfcomm_dev, context, error); +} + +static void +_connect_dun_step1_cb (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + gs_unref_variant GVariant *ret = NULL; + gs_free_error GError *error = NULL; + BzDBusObj *bzobj_adapter; + BzDBusObj *bzobj; + + ret = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source_object), res, &error); + + if ( !ret + && nm_utils_error_is_cancelled (error, FALSE)) + return; + + bzobj = user_data; + + if (error) { + _LOGT ("DUN: [%s]: bluetooth device connect failed: %s", bzobj->object_path, error->message); + /* we actually ignore this error. Let's try, maybe we still can connect via DUN. */ + g_clear_error (&error); + } else + _LOGT ("DUN: [%s]: bluetooth device connected successfully", bzobj->object_path); + + if (!_bzobjs_device_is_usable (bzobj, &bzobj_adapter, NULL)) { + nm_utils_error_set (&error, NM_UTILS_ERROR_UNKNOWN, + "device %s is not usable for DUN after connect", + bzobj->object_path); + _connect_returned (bzobj->self, bzobj, NM_BT_CAPABILITY_DUN, NULL, NULL, error); + return; + } + + if (!nm_bluez5_dun_connect (bzobj_adapter->d_adapter.address, + bzobj->d_device.address, + bzobj->x_device.c_req_data->int_cancellable, + _connect_dun_step2_cb, + bzobj, + _connect_dun_notify_tty_hangup_cb, + bzobj, + &error)) { + _connect_returned (bzobj->self, bzobj, NM_BT_CAPABILITY_DUN, NULL, NULL, error); + return; + } +} +#endif + +static void +_connect_nap_cb (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + gs_unref_variant GVariant *ret = NULL; + const char *network_iface_name = NULL; + gs_free_error GError *error = NULL; + BzDBusObj *bzobj; + + ret = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source_object), res, &error); + + if ( !ret + && nm_utils_error_is_cancelled (error, FALSE)) + return; + + if (ret) + g_variant_get (ret, "(&s)", &network_iface_name); + + bzobj = user_data; + + _connect_returned (bzobj->self, bzobj, NM_BT_CAPABILITY_NAP, network_iface_name, NULL, error); +} + +static void +_connect_cancelled_cb (GCancellable *cancellable, + BzDBusObj *bzobj) +{ + _connect_disconnect (bzobj->self, bzobj, "connect cancelled"); +} + +static gboolean +_connect_timeout_wait_connected_cb (gpointer user_data) +{ + BzDBusObj *bzobj = user_data; + + bzobj->x_device.c_req_data->timeout_wait_connect_id = 0; + _connect_disconnect (bzobj->self, bzobj, "timeout waiting for connected"); + return G_SOURCE_REMOVE; +} + +static gboolean +_connect_timeout_cb (gpointer user_data) +{ + BzDBusObj *bzobj = user_data; + + bzobj->x_device.c_req_data->timeout_id = 0; + _connect_disconnect (bzobj->self, bzobj, "timeout connecting"); + return G_SOURCE_REMOVE; } +static void +_connect_disconnect (NMBluezManager *self, + BzDBusObj *bzobj, + const char *reason) +{ + NMBluezManagerPrivate *priv = NM_BLUEZ_MANAGER_GET_PRIVATE (self); + DeviceConnectReqData *c_req_data; + char sbuf_cap[100]; + gboolean bt_type; + + if (bzobj->x_device_connect_bt_type == NM_BT_CAPABILITY_NONE) { + nm_assert (!bzobj->x_device.c_req_data); + return; + } + + bt_type = bzobj->x_device_connect_bt_type; + nm_assert (NM_IN_SET (bt_type, NM_BT_CAPABILITY_DUN, NM_BT_CAPABILITY_NAP)); + bzobj->x_device_connect_bt_type = NM_BT_CAPABILITY_NONE; + + c_req_data = g_steal_pointer (&bzobj->x_device.c_req_data); + + _LOGD ("%s [%s]: disconnect due to %s", + nm_bluetooth_capability_to_string (bt_type, sbuf_cap, sizeof (sbuf_cap)), + bzobj->object_path, + reason); + + if (c_req_data) + nm_clear_g_cancellable (&c_req_data->int_cancellable); + + if (bt_type == NM_BT_CAPABILITY_DUN) { + /* For DUN devices, we also called org.bluez.Device1.Connect() (because in order + * for nm_bluez5_dun_connect() to succeed, we need to be already connected *why??). + * + * But upon disconnect we don't call Disconnect() because we don't know whether somebody + * else also uses the bluetooth device for other purposes. During disconnect we only + * terminate the DUN connection, but don't disconnect entirely. I think that's the + * best we can do. */ +#if WITH_BLUEZ5_DUN + nm_clear_pointer (&bzobj->x_device.connect_dun_context, nm_bluez5_dun_disconnect); +#else + nm_assert_not_reached (); +#endif + } else { + if (priv->name_owner) { + gs_unref_object GCancellable *cancellable = NULL; + + cancellable = g_cancellable_new (); + + nm_shutdown_wait_obj_register_cancellable_full (cancellable, + g_strdup_printf ("bt-disconnect-nap[%s]", bzobj->object_path), + TRUE); + + g_dbus_connection_call (priv->dbus_connection, + priv->name_owner, + bzobj->object_path, + NM_BLUEZ5_NETWORK_INTERFACE, + "Disconnect", + g_variant_new("()"), + NULL, + G_DBUS_CALL_FLAGS_NO_AUTO_START, + -1, + cancellable, + _dbus_call_complete_cb_nop, + NULL); + } + } + + if (c_req_data) { + gs_free_error GError *error = NULL; + + nm_utils_error_set (&error, + NM_UTILS_ERROR_UNKNOWN, + "connect aborted due to %s", + reason); + _device_connect_req_data_complete (c_req_data, self, NULL, error); + } +} + +gboolean +nm_bluez_manager_connect (NMBluezManager *self, + const char *object_path, + NMBluetoothCapabilities connection_bt_type, + int timeout_msec, + GCancellable *cancellable, + NMBluezManagerConnectCb callback, + gpointer callback_user_data, + GError **error) +{ + gs_unref_object GCancellable *int_cancellable = NULL; + DeviceConnectReqData *c_req_data; + NMBluezManagerPrivate *priv; + BzDBusObj *bzobj; + char sbuf_cap[100]; + + g_return_val_if_fail (NM_IS_BLUEZ_MANAGER (self), FALSE); + g_return_val_if_fail (NM_IN_SET (connection_bt_type, NM_BT_CAPABILITY_DUN, + NM_BT_CAPABILITY_NAP), FALSE); + g_return_val_if_fail (callback, FALSE); + + nm_assert (timeout_msec > 0); + + priv = NM_BLUEZ_MANAGER_GET_PRIVATE (self); + + bzobj = _bzobjs_get (self, object_path); + + if (!bzobj) { + nm_utils_error_set (error, NM_UTILS_ERROR_UNKNOWN, + "device %s does not exist", + object_path); + return FALSE; + } + + if (!_bzobjs_device_is_usable (bzobj, NULL, NULL)) { + nm_utils_error_set (error, NM_UTILS_ERROR_UNKNOWN, + "device %s is not usable", + object_path); + return FALSE; + } + + if (!NM_FLAGS_ALL (bzobj->d_device_capabilities, connection_bt_type)) { + nm_utils_error_set (error, NM_UTILS_ERROR_UNKNOWN, + "device %s has not the required capabilities", + object_path); + return FALSE; + } + +#if !WITH_BLUEZ5_DUN + if (connection_bt_type == NM_BT_CAPABILITY_DUN) { + nm_utils_error_set (error, NM_UTILS_ERROR_UNKNOWN, + "DUN is not supported"); + return FALSE; + } +#endif + + _connect_disconnect (self, bzobj, "new activation"); + + _LOGD ("%s [%s]: connecting...", + nm_bluetooth_capability_to_string (connection_bt_type, sbuf_cap, sizeof (sbuf_cap)), + bzobj->object_path); + + int_cancellable = g_cancellable_new(); + +#if WITH_BLUEZ5_DUN + if (connection_bt_type == NM_BT_CAPABILITY_DUN) { + g_dbus_connection_call (priv->dbus_connection, + priv->name_owner, + bzobj->object_path, + NM_BLUEZ5_DEVICE_INTERFACE, + "Connect", + NULL, + NULL, + G_DBUS_CALL_FLAGS_NO_AUTO_START, + timeout_msec, + int_cancellable, + _connect_dun_step1_cb, + bzobj); + } else +#endif + { + nm_assert (connection_bt_type == NM_BT_CAPABILITY_NAP); + g_dbus_connection_call (priv->dbus_connection, + priv->name_owner, + bzobj->object_path, + NM_BLUEZ5_NETWORK_INTERFACE, + "Connect", + g_variant_new ("(s)", BLUETOOTH_CONNECT_NAP), + G_VARIANT_TYPE ("(s)"), + G_DBUS_CALL_FLAGS_NO_AUTO_START, + timeout_msec, + int_cancellable, + _connect_nap_cb, + bzobj); + } + + c_req_data = g_slice_new (DeviceConnectReqData); + *c_req_data = (DeviceConnectReqData) { + .int_cancellable = g_steal_pointer (&int_cancellable), + .ext_cancellable = g_object_ref (cancellable), + .callback = callback, + .callback_user_data = callback_user_data, + .ext_cancelled_id = g_signal_connect (cancellable, + "cancelled", + G_CALLBACK (_connect_cancelled_cb), + bzobj), + .timeout_id = g_timeout_add (timeout_msec, + _connect_timeout_cb, + bzobj), + }; + + bzobj->x_device_connect_bt_type = connection_bt_type; + bzobj->x_device.c_req_data = c_req_data; + + return TRUE; +} + +void +nm_bluez_manager_disconnect (NMBluezManager *self, + const char *object_path) +{ + BzDBusObj *bzobj; + + g_return_if_fail (NM_IS_BLUEZ_MANAGER (self)); + g_return_if_fail (object_path); + + bzobj = _bzobjs_get (self, object_path); + if (!bzobj) + return; + + _connect_disconnect (self, bzobj, "disconnected by user"); +} + +/*****************************************************************************/ + static NMDevice * create_device (NMDeviceFactory *factory, const char *iface, @@ -334,8 +2790,8 @@ create_device (NMDeviceFactory *factory, NMConnection *connection, gboolean *out_ignore) { - g_warn_if_fail (plink->type == NM_LINK_TYPE_BNEP); *out_ignore = TRUE; + g_return_val_if_fail (plink->type == NM_LINK_TYPE_BNEP, NULL); return NULL; } @@ -360,7 +2816,25 @@ nm_bluez_manager_init (NMBluezManager *self) { NMBluezManagerPrivate *priv = NM_BLUEZ_MANAGER_GET_PRIVATE (self); + priv->vtable_network_server = (NMBtVTableNetworkServer) { + .is_available = _network_server_vt_is_available, + .register_bridge = _network_server_vt_register_bridge, + .unregister_bridge = _network_server_vt_unregister_bridge, + }; + + c_list_init (&priv->network_server_lst_head); + c_list_init (&priv->process_change_lst_head); + + priv->conn_data_heads = g_hash_table_new_full (_conn_data_head_hash, _conn_data_head_equal, g_free, NULL); + priv->conn_data_elems = g_hash_table_new_full (nm_pdirect_hash, nm_pdirect_equal, nm_g_slice_free_fcn (ConnDataElem), NULL); + + priv->bzobjs = g_hash_table_new_full (nm_pstr_hash, nm_pstr_equal, (GDestroyNotify) _bz_dbus_obj_free, NULL); + + priv->manager = g_object_ref (NM_MANAGER_GET); priv->settings = g_object_ref (NM_SETTINGS_GET); + priv->dbus_connection = nm_g_object_ref (NM_MAIN_DBUS_CONNECTION_GET); + + g_atomic_pointer_compare_and_exchange (&nm_bt_vtable_network_server, NULL, &priv->vtable_network_server); } static void @@ -369,16 +2843,25 @@ dispose (GObject *object) NMBluezManager *self = NM_BLUEZ_MANAGER (object); NMBluezManagerPrivate *priv = NM_BLUEZ_MANAGER_GET_PRIVATE (self); - if (priv->manager5) { - g_signal_handlers_disconnect_by_data (priv->manager5, self); - g_clear_object (&priv->manager5); - } + /* FIXME(shutdown): we need a nm_device_factory_stop() hook to first unregister all + * BzDBusObj instances and do necessary cleanup actions (like disconnecting devices + * or deleting panu_connection). */ + + nm_assert (c_list_is_empty (&priv->network_server_lst_head)); + nm_assert (c_list_is_empty (&priv->process_change_lst_head)); + nm_assert (priv->process_change_idle_id == 0); - cleanup_checking (self, TRUE); + g_atomic_pointer_compare_and_exchange (&nm_bt_vtable_network_server, &priv->vtable_network_server, NULL); + + _cleanup_all (self); G_OBJECT_CLASS (nm_bluez_manager_parent_class)->dispose (object); g_clear_object (&priv->settings); + g_clear_object (&priv->manager); + g_clear_object (&priv->dbus_connection); + + nm_clear_pointer (&priv->bzobjs, g_hash_table_destroy); } static void @@ -387,10 +2870,10 @@ nm_bluez_manager_class_init (NMBluezManagerClass *klass) GObjectClass *object_class = G_OBJECT_CLASS (klass); NMDeviceFactoryClass *factory_class = NM_DEVICE_FACTORY_CLASS (klass); - object_class->dispose = dispose; + object_class->dispose = dispose; factory_class->get_supported_types = get_supported_types; - factory_class->create_device = create_device; - factory_class->match_connection = match_connection; - factory_class->start = start; + factory_class->create_device = create_device; + factory_class->match_connection = match_connection; + factory_class->start = start; } diff --git a/src/devices/bluetooth/nm-bluez-manager.h b/src/devices/bluetooth/nm-bluez-manager.h new file mode 100644 index 0000000000..85dbaa838a --- /dev/null +++ b/src/devices/bluetooth/nm-bluez-manager.h @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: LGPL-2.1+ +/* + * Copyright (C) 2009 - 2019 Red Hat, Inc. + */ + +#ifndef __NM_BLUEZ_MANAGER_H__ +#define __NM_BLUEZ_MANAGER_H__ + +#define NM_TYPE_BLUEZ_MANAGER (nm_bluez_manager_get_type ()) +#define NM_BLUEZ_MANAGER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_BLUEZ_MANAGER, NMBluezManager)) +#define NM_BLUEZ_MANAGER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), NM_TYPE_BLUEZ_MANAGER, NMBluezManagerClass)) +#define NM_IS_BLUEZ_MANAGER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_BLUEZ_MANAGER)) +#define NM_IS_BLUEZ_MANAGER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NM_TYPE_BLUEZ_MANAGER)) +#define NM_BLUEZ_MANAGER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), NM_TYPE_BLUEZ_MANAGER, NMBluezManagerClass)) + +typedef struct _NMBluezManager NMBluezManager; +typedef struct _NMBluezManagerClass NMBluezManagerClass; + +GType nm_bluez_manager_get_type (void); + +typedef void (*NMBluezManagerConnectCb) (NMBluezManager *self, + gboolean is_completed /* or else is early notification with DUN path */, + const char *device_name, + GError *error, + gpointer user_data); + +gboolean nm_bluez_manager_connect (NMBluezManager *self, + const char *object_path, + NMBluetoothCapabilities connection_bt_type, + int timeout_msec, + GCancellable *cancellable, + NMBluezManagerConnectCb callback, + gpointer callback_user_data, + GError **error); + +void nm_bluez_manager_disconnect (NMBluezManager *self, + const char *object_path); + +#endif /* __NM_BLUEZ_MANAGER_H__ */ diff --git a/src/devices/bluetooth/nm-bluez5-dun.c b/src/devices/bluetooth/nm-bluez5-dun.c index 1338bcb811..3038e2635f 100644 --- a/src/devices/bluetooth/nm-bluez5-dun.c +++ b/src/devices/bluetooth/nm-bluez5-dun.c @@ -19,145 +19,407 @@ #include "nm-bt-error.h" #include "NetworkManagerUtils.h" +#define RFCOMM_FMT "/dev/rfcomm%d" + +/*****************************************************************************/ + +typedef struct { + GCancellable *cancellable; + NMBluez5DunConnectCb callback; + gpointer callback_user_data; + + sdp_session_t *sdp_session; + + GError *rfcomm_sdp_search_error; + + gint64 connect_open_tty_started_at; + + gulong cancelled_id; + + guint source_id; + + guint8 sdp_session_try_count; +} ConnectData; + struct _NMBluez5DunContext { + const char *dst_str; + + ConnectData *cdat; + + NMBluez5DunNotifyTtyHangupCb notify_tty_hangup_cb; + gpointer notify_tty_hangup_user_data; + + char *rfcomm_tty_path; + + int rfcomm_sock_fd; + int rfcomm_tty_fd; + int rfcomm_tty_no; + int rfcomm_channel; + + guint rfcomm_tty_poll_id; + bdaddr_t src; bdaddr_t dst; - char *src_str; - char *dst_str; - int rfcomm_channel; - int rfcomm_fd; - int rfcomm_tty_fd; - int rfcomm_id; - NMBluez5DunFunc callback; - gpointer user_data; - sdp_session_t *sdp_session; - guint sdp_watch_id; + + char src_str[]; }; -static void -dun_connect (NMBluez5DunContext *context) +/*****************************************************************************/ + +#define _NMLOG_DOMAIN LOGD_BT +#define _NMLOG_PREFIX_NAME "bluez" +#define _NMLOG(level, context, ...) \ + G_STMT_START { \ + if (nm_logging_enabled ((level), (_NMLOG_DOMAIN))) { \ + const NMBluez5DunContext *const _context = (context); \ + \ + _nm_log ((level), (_NMLOG_DOMAIN), 0, NULL, NULL, \ + "%s: DUN[%s] " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \ + _NMLOG_PREFIX_NAME, \ + _context->src_str \ + _NM_UTILS_MACRO_REST(__VA_ARGS__)); \ + } \ + } G_STMT_END + +/*****************************************************************************/ + +static void _context_invoke_callback_success (NMBluez5DunContext *context); +static void _context_invoke_callback_fail_and_free (NMBluez5DunContext *context, + GError *error); +static void _context_free (NMBluez5DunContext *context); +static int _connect_open_tty (NMBluez5DunContext *context); +static gboolean _connect_sdp_session_start (NMBluez5DunContext *context, + GError **error); + +/*****************************************************************************/ + +NM_AUTO_DEFINE_FCN0 (NMBluez5DunContext *, _nm_auto_free_context, _context_free) +#define nm_auto_free_context nm_auto(_nm_auto_free_context) + +/*****************************************************************************/ + +const char * +nm_bluez5_dun_context_get_adapter (const NMBluez5DunContext *context) { - struct sockaddr_rc sa; - int devid, try = 30; - char tty[100]; - const int ttylen = sizeof (tty) - 1; - GError *error = NULL; - int errsv; + return context->src_str; +} - struct rfcomm_dev_req req = { - .flags = (1 << RFCOMM_REUSE_DLC) | (1 << RFCOMM_RELEASE_ONHUP), - .dev_id = -1, - .channel = context->rfcomm_channel - }; +const char * +nm_bluez5_dun_context_get_remote (const NMBluez5DunContext *context) +{ + return context->dst_str; +} - context->rfcomm_fd = socket (AF_BLUETOOTH, SOCK_STREAM | SOCK_CLOEXEC, BTPROTO_RFCOMM); - if (context->rfcomm_fd < 0) { - errsv = errno; - error = g_error_new (NM_BT_ERROR, NM_BT_ERROR_DUN_CONNECT_FAILED, - "Failed to create RFCOMM socket: (%d) %s", - errsv, nm_strerror_native (errsv)); - goto done; - } +const char * +nm_bluez5_dun_context_get_rfcomm_dev (const NMBluez5DunContext *context) +{ + return context->rfcomm_tty_path; +} - /* Connect to the remote device */ - sa.rc_family = AF_BLUETOOTH; - sa.rc_channel = 0; - memcpy (&sa.rc_bdaddr, &context->src, ETH_ALEN); - if (bind (context->rfcomm_fd, (struct sockaddr *) &sa, sizeof(sa))) { - errsv = errno; - error = g_error_new (NM_BT_ERROR, NM_BT_ERROR_DUN_CONNECT_FAILED, - "Failed to bind socket: (%d) %s", - errsv, nm_strerror_native (errsv)); - goto done; +/*****************************************************************************/ + +static gboolean +_rfcomm_tty_poll_cb (GIOChannel *stream, + GIOCondition condition, + gpointer user_data) +{ + NMBluez5DunContext *context = user_data; + + _LOGD (context, "receive %s%s%s signal on rfcomm file descriptor", + NM_FLAGS_HAS (condition, G_IO_ERR) ? "ERR" : "", + NM_FLAGS_ALL (condition, G_IO_HUP | G_IO_ERR) ? "," : "", + NM_FLAGS_HAS (condition, G_IO_HUP) ? "HUP" : ""); + + context->rfcomm_tty_poll_id = 0; + context->notify_tty_hangup_cb (context, + context->notify_tty_hangup_user_data); + return G_SOURCE_REMOVE; +} + +static gboolean +_connect_open_tty_retry_cb (gpointer user_data) +{ + NMBluez5DunContext *context = user_data; + int r; + + r = _connect_open_tty (context); + if (r >= 0) + return G_SOURCE_REMOVE; + + if (nm_utils_get_monotonic_timestamp_ns () > context->cdat->connect_open_tty_started_at + (30 * 100 * NM_UTILS_NS_PER_MSEC)) { + gs_free_error GError *error = NULL; + + context->cdat->source_id = 0; + g_set_error (&error, + NM_BT_ERROR, + NM_BT_ERROR_DUN_CONNECT_FAILED, + "give up waiting to open %s device: %s (%d)", + context->rfcomm_tty_path, + nm_strerror_native (r), + -r); + _context_invoke_callback_fail_and_free (context, error); + return G_SOURCE_REMOVE; } - sa.rc_channel = context->rfcomm_channel; - memcpy (&sa.rc_bdaddr, &context->dst, ETH_ALEN); - if (connect (context->rfcomm_fd, (struct sockaddr *) &sa, sizeof (sa)) ) { - errsv = errno; - error = g_error_new (NM_BT_ERROR, NM_BT_ERROR_DUN_CONNECT_FAILED, - "Failed to connect to remote device: (%d) %s", - errsv, nm_strerror_native (errsv)); - goto done; + return G_SOURCE_CONTINUE; +} + +static int +_connect_open_tty (NMBluez5DunContext *context) +{ + nm_auto_unref_io_channel GIOChannel *io_channel = NULL; + int fd; + int errsv; + + fd = open (context->rfcomm_tty_path, O_RDONLY | O_NOCTTY | O_CLOEXEC); + if (fd < 0) { + errsv = NM_ERRNO_NATIVE (errno); + + if (context->cdat->source_id == 0) { + _LOGD (context, "failed opening tty "RFCOMM_FMT": %s (%d). Start polling...", + context->rfcomm_tty_no, + nm_strerror_native (errsv), + errsv); + context->cdat->connect_open_tty_started_at = nm_utils_get_monotonic_timestamp_ns (); + context->cdat->source_id = g_timeout_add (100, + _connect_open_tty_retry_cb, + context); + } + return -errsv; } - nm_log_dbg (LOGD_BT, "(%s): connected to %s on channel %d", - context->src_str, context->dst_str, context->rfcomm_channel); + context->rfcomm_tty_fd = fd; + + io_channel = g_io_channel_unix_new (context->rfcomm_tty_fd); + context->rfcomm_tty_poll_id = g_io_add_watch (io_channel, + G_IO_ERR | G_IO_HUP, + _rfcomm_tty_poll_cb, + context); + + _context_invoke_callback_success (context); + return 0; +} + +static void +_connect_create_rfcomm (NMBluez5DunContext *context) +{ + gs_free_error GError *error = NULL; + struct rfcomm_dev_req req; + int devid; + int errsv; + int r; + + _LOGD (context, "connected to %s on channel %d", + context->dst_str, context->rfcomm_channel); /* Create an RFCOMM kernel device for the DUN channel */ + memset (&req, 0, sizeof (req)); + req.dev_id = -1; + req.flags = (1 << RFCOMM_REUSE_DLC) | (1 << RFCOMM_RELEASE_ONHUP); + req.channel = context->rfcomm_channel; memcpy (&req.src, &context->src, ETH_ALEN); memcpy (&req.dst, &context->dst, ETH_ALEN); - devid = ioctl (context->rfcomm_fd, RFCOMMCREATEDEV, &req); + devid = ioctl (context->rfcomm_sock_fd, RFCOMMCREATEDEV, &req); if (devid < 0) { - errsv = errno; - error = g_error_new (NM_BT_ERROR, NM_BT_ERROR_DUN_CONNECT_FAILED, - "Failed to create rfcomm device: (%d) %s", - errsv, nm_strerror_native (errsv)); - goto done; + errsv = NM_ERRNO_NATIVE (errno); + if (errsv == EBADFD) { + /* hm. We use a non-blocking socket to connect. Above getsockopt(SOL_SOCKET,SO_ERROR) indicated + * success, but still now we fail with EBADFD. I think that is a bug and we should get the + * failure during connect(). + * + * Anyway, craft a less confusing error message than + * "failed to create rfcomm device: File descriptor in bad state (77)". */ + g_set_error (&error, + NM_BT_ERROR, + NM_BT_ERROR_DUN_CONNECT_FAILED, + "unknown failure to connect to DUN device"); + } else { + g_set_error (&error, + NM_BT_ERROR, + NM_BT_ERROR_DUN_CONNECT_FAILED, + "failed to create rfcomm device: %s (%d)", + nm_strerror_native (errsv), errsv); + } + _context_invoke_callback_fail_and_free (context, error); + return; } - context->rfcomm_id = devid; - snprintf (tty, ttylen, "/dev/rfcomm%d", devid); - while ((context->rfcomm_tty_fd = open (tty, O_RDONLY | O_NOCTTY | O_CLOEXEC)) < 0 && try--) { - if (try) { - g_usleep (100 * 1000); - continue; - } + context->rfcomm_tty_no = devid; + context->rfcomm_tty_path = g_strdup_printf (RFCOMM_FMT, devid); + + r = _connect_open_tty (context); + if (r < 0) { + /* we created the rfcomm device, but cannot yet open it. That means, we are + * not yet fully connected. However, we notify the caller about "what we learned + * so far". Note that this happens synchronously. + * + * The purpose is that once we proceed synchrnously, modem-manager races with + * the detection of the modem. We want to notify the caller first about the + * device name. */ + context->cdat->callback (NULL, + context->rfcomm_tty_path, + NULL, + context->cdat->callback_user_data); + } +} - error = g_error_new (NM_BT_ERROR, NM_BT_ERROR_DUN_CONNECT_FAILED, - "Failed to find rfcomm device: %s", - tty); - break; +static gboolean +_connect_socket_connect_cb (GIOChannel *stream, + GIOCondition condition, + gpointer user_data) +{ + NMBluez5DunContext *context = user_data; + gs_free GError *error = NULL; + int errsv = 0; + socklen_t slen = sizeof(errsv); + int r; + + context->cdat->source_id = 0; + + r = getsockopt (context->rfcomm_sock_fd, SOL_SOCKET, SO_ERROR, &errsv, &slen); + + if (r < 0) { + errsv = errno; + g_set_error (&error, + NM_BT_ERROR, + NM_BT_ERROR_DUN_CONNECT_FAILED, + "failed to complete connecting RFCOMM socket: %s (%d)", + nm_strerror_native (errsv), errsv); + _context_invoke_callback_fail_and_free (context, error); + return G_SOURCE_REMOVE; } -done: - context->callback (context, tty, error, context->user_data); + if (errsv != 0) { + g_set_error (&error, + NM_BT_ERROR, + NM_BT_ERROR_DUN_CONNECT_FAILED, + "failed to connect RFCOMM socket: %s (%d)", + nm_strerror_native (errsv), errsv); + _context_invoke_callback_fail_and_free (context, error); + return G_SOURCE_REMOVE; + } + + _connect_create_rfcomm (context); + return G_SOURCE_REMOVE; } static void -sdp_search_cleanup (NMBluez5DunContext *context) +_connect_socket_connect (NMBluez5DunContext *context) { - if (context->sdp_session) { - sdp_close (context->sdp_session); - context->sdp_session = NULL; + gs_free_error GError *error = NULL; + struct sockaddr_rc sa; + int errsv; + + context->rfcomm_sock_fd = socket (AF_BLUETOOTH, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, BTPROTO_RFCOMM); + if (context->rfcomm_sock_fd < 0) { + errsv = errno; + g_set_error (&error, + NM_BT_ERROR, + NM_BT_ERROR_DUN_CONNECT_FAILED, + "failed to create RFCOMM socket: %s (%d)", + nm_strerror_native (errsv), errsv); + _context_invoke_callback_fail_and_free (context, error); + return; + } + + /* Connect to the remote device */ + memset (&sa, 0, sizeof (sa)); + sa.rc_family = AF_BLUETOOTH; + sa.rc_channel = 0; + memcpy (&sa.rc_bdaddr, &context->src, ETH_ALEN); + if (bind (context->rfcomm_sock_fd, + (struct sockaddr *) &sa, + sizeof(sa)) != 0) { + errsv = errno; + g_set_error (&error, + NM_BT_ERROR, + NM_BT_ERROR_DUN_CONNECT_FAILED, + "failed to bind socket: %s (%d)", + nm_strerror_native (errsv), errsv); + _context_invoke_callback_fail_and_free (context, error); + return; + } + + memset (&sa, 0, sizeof (sa)); + sa.rc_family = AF_BLUETOOTH; + sa.rc_channel = context->rfcomm_channel; + memcpy (&sa.rc_bdaddr, &context->dst, ETH_ALEN); + if (connect (context->rfcomm_sock_fd, + (struct sockaddr *) &sa, + sizeof (sa)) != 0) { + nm_auto_unref_io_channel GIOChannel *io_channel = NULL; + + errsv = errno; + if (errsv != EINPROGRESS) { + g_set_error (&error, + NM_BT_ERROR, + NM_BT_ERROR_DUN_CONNECT_FAILED, + "failed to connect to remote device: %s (%d)", + nm_strerror_native (errsv), errsv); + _context_invoke_callback_fail_and_free (context, error); + return; + } + + _LOGD (context, "connecting to %s on channel %d...", + context->dst_str, + context->rfcomm_channel); + + io_channel = g_io_channel_unix_new (context->rfcomm_sock_fd); + context->cdat->source_id = g_io_add_watch (io_channel, + G_IO_OUT, + _connect_socket_connect_cb, + context); + return; } - nm_clear_g_source (&context->sdp_watch_id); + _connect_create_rfcomm (context); } static void -sdp_search_completed_cb (uint8_t type, uint16_t status, uint8_t *rsp, size_t size, void *user_data) +_connect_sdp_search_cb (uint8_t type, + uint16_t status, + uint8_t *rsp, + size_t size, + void *user_data) { NMBluez5DunContext *context = user_data; - int scanned, seqlen = 0, bytesleft = size; + int scanned; + int seqlen = 0; + int bytesleft = size; uint8_t dataType; int channel = -1; - nm_log_dbg (LOGD_BT, "(%s -> %s): SDP search finished with type=%d status=%d", - context->src_str, context->dst_str, status, type); + if ( context->cdat->rfcomm_sdp_search_error + || context->rfcomm_channel >= 0) + return; + + _LOGD (context, "SDP search finished with type=%d status=%d", + status, type); /* SDP response received */ - if (status || type != SDP_SVC_SEARCH_ATTR_RSP) { - GError *error = g_error_new (NM_BT_ERROR, - NM_BT_ERROR_DUN_CONNECT_FAILED, - "Did not get a Service Discovery response"); - context->callback (context, NULL, error, context->user_data); - goto done; + if ( status + || type != SDP_SVC_SEARCH_ATTR_RSP) { + g_set_error (&context->cdat->rfcomm_sdp_search_error, + NM_BT_ERROR, + NM_BT_ERROR_DUN_CONNECT_FAILED, + "did not get a Service Discovery response"); + return; } scanned = sdp_extract_seqtype (rsp, bytesleft, &dataType, &seqlen); - nm_log_dbg (LOGD_BT, "(%s -> %s): SDP sequence type scanned=%d length=%d", - context->src_str, context->dst_str, scanned, seqlen); + _LOGD (context, "SDP sequence type scanned=%d length=%d", + scanned, seqlen); scanned = sdp_extract_seqtype (rsp, bytesleft, &dataType, &seqlen); - if (!scanned || !seqlen) { + if ( !scanned + || !seqlen) { /* Short read or unknown sequence type */ - GError *error = g_error_new (NM_BT_ERROR, - NM_BT_ERROR_DUN_CONNECT_FAILED, - "Improper Service Discovery response"); - context->callback (context, NULL, error, context->user_data); - goto done; + g_set_error (&context->cdat->rfcomm_sdp_search_error, + NM_BT_ERROR, + NM_BT_ERROR_DUN_CONNECT_FAILED, + "improper Service Discovery response"); + return; } rsp += scanned; @@ -181,90 +443,153 @@ sdp_search_completed_cb (uint8_t type, uint16_t status, uint8_t *rsp, size_t siz channel = sdp_get_proto_port (protos, RFCOMM_UUID); sdp_list_free (protos, NULL); - nm_log_dbg (LOGD_BT, "(%s -> %s): SDP channel=%d", - context->src_str, context->dst_str, channel); + _LOGD (context, "SDP channel=%d", + channel); } sdp_record_free (rec); scanned += recsize; rsp += recsize; bytesleft -= recsize; - } while ((scanned < (ssize_t) size) && (bytesleft > 0) && (channel < 0)); - -done: - if (channel != -1) { - context->rfcomm_channel = channel; - dun_connect (context); + } while ( scanned < (ssize_t) size + && bytesleft > 0 + && channel < 0); + + if (channel == -1) { + g_set_error (&context->cdat->rfcomm_sdp_search_error, + NM_BT_ERROR, + NM_BT_ERROR_DUN_CONNECT_FAILED, + "did not receive rfcomm-channel"); + return; } - sdp_search_cleanup (context); + context->rfcomm_channel = channel; } static gboolean -sdp_search_process_cb (GIOChannel *channel, GIOCondition condition, gpointer user_data) +_connect_sdp_search_io_cb (GIOChannel *io_channel, + GIOCondition condition, + gpointer user_data) { NMBluez5DunContext *context = user_data; - - nm_log_dbg (LOGD_BT, "(%s -> %s): SDP search progressed with condition=%d", - context->src_str, context->dst_str, condition); + gs_free GError *error = NULL; + int errsv; if (condition & (G_IO_ERR | G_IO_HUP | G_IO_NVAL)) { - GError *error = g_error_new (NM_BT_ERROR, - NM_BT_ERROR_DUN_CONNECT_FAILED, - "Service Discovery interrupted"); - context->callback (context, NULL, error, context->user_data); - sdp_search_cleanup (context); - return FALSE; + _LOGD (context, "SDP search returned with invalid IO condition 0x%x", + (guint) condition); + error = g_error_new (NM_BT_ERROR, + NM_BT_ERROR_DUN_CONNECT_FAILED, + "Service Discovery interrupted"); + context->cdat->source_id = 0; + _context_invoke_callback_fail_and_free (context, error); + return G_SOURCE_REMOVE; } - if (sdp_process (context->sdp_session) < 0) { - nm_log_dbg (LOGD_BT, "(%s -> %s): SDP search finished", - context->src_str, context->dst_str); + if (sdp_process (context->cdat->sdp_session) == 0) { + _LOGD (context, "SDP search still not finished"); + return G_SOURCE_CONTINUE; + } - /* Search finished successfully. */ - return FALSE; + context->cdat->source_id = 0; + + if ( context->rfcomm_channel < 0 + && !context->cdat->rfcomm_sdp_search_error) { + errsv = sdp_get_error (context->cdat->sdp_session); + _LOGD (context, "SDP search failed: %s (%d)", + nm_strerror_native (errsv), errsv); + error = g_error_new (NM_BT_ERROR, + NM_BT_ERROR_DUN_CONNECT_FAILED, + "Service Discovery failed with %s (%d)", + nm_strerror_native (errsv), errsv); + _context_invoke_callback_fail_and_free (context, error); + return G_SOURCE_REMOVE; } - /* Search progressed successfully. */ - return TRUE; + if (context->cdat->rfcomm_sdp_search_error) { + _LOGD (context, "SDP search failed to complete: %s", context->cdat->rfcomm_sdp_search_error->message); + _context_invoke_callback_fail_and_free (context, context->cdat->rfcomm_sdp_search_error); + return G_SOURCE_REMOVE; + } + + nm_clear_pointer (&context->cdat->sdp_session, sdp_close); + + _connect_socket_connect (context); + + return G_SOURCE_REMOVE; } static gboolean -sdp_connect_watch (GIOChannel *channel, GIOCondition condition, gpointer user_data) +_connect_sdp_session_start_on_idle_cb (gpointer user_data) { NMBluez5DunContext *context = user_data; - sdp_list_t *search, *attrs; + gs_free_error GError *error = NULL; + + context->cdat->source_id = 0; + + _LOGD (context, "retry starting sdp-session..."); + + if (!_connect_sdp_session_start (context, &error)) + _context_invoke_callback_fail_and_free (context, error); + + return G_SOURCE_REMOVE; +} + +static gboolean +_connect_sdp_io_cb (GIOChannel *io_channel, + GIOCondition condition, + gpointer user_data) +{ + NMBluez5DunContext *context = user_data; + sdp_list_t *search; + sdp_list_t *attrs; uuid_t svclass; uint16_t attr; - int fd, fd_err = 0; - int err; + int fd; + int errsv; + int fd_err = 0; + int r; socklen_t len = sizeof (fd_err); - GError *error = NULL; + gs_free_error GError *error = NULL; + + context->cdat->source_id = 0; - context->sdp_watch_id = 0; + fd = g_io_channel_unix_get_fd (io_channel); + + _LOGD (context, "sdp-session ready to connect with fd=%d", fd); - fd = g_io_channel_unix_get_fd (channel); if (getsockopt (fd, SOL_SOCKET, SO_ERROR, &fd_err, &len) < 0) { - err = errno; - nm_log_dbg (LOGD_BT, "(%s -> %s): getsockopt error=%d", - context->src_str, context->dst_str, err); - } else { - err = fd_err; - nm_log_dbg (LOGD_BT, "(%s -> %s): SO_ERROR error=%d", - context->src_str, context->dst_str, fd_err); + errsv = NM_ERRNO_NATIVE (errno); + error = g_error_new (NM_BT_ERROR, NM_BT_ERROR_DUN_CONNECT_FAILED, + "error for getsockopt on Service Discovery socket: %s (%d)", + nm_strerror_native (errsv), errsv); + goto done; } - if (err != 0) { + if (fd_err != 0) { + errsv = nm_errno_native (fd_err); + + if ( NM_IN_SET (errsv, ECONNREFUSED, EHOSTDOWN) + && --context->cdat->sdp_session_try_count > 0) { + /* *sigh* */ + _LOGD (context, "sdp-session failed with %s (%d). Retry in a bit", nm_strerror_native (errsv), errsv); + nm_clear_g_source (&context->cdat->source_id); + context->cdat->source_id = g_timeout_add (1000, + _connect_sdp_session_start_on_idle_cb, + context); + return G_SOURCE_REMOVE; + } + error = g_error_new (NM_BT_ERROR, NM_BT_ERROR_DUN_CONNECT_FAILED, - "Error on Service Discovery socket: (%d) %s", - err, nm_strerror_native (err)); + "error on Service Discovery socket: %s (%d)", + nm_strerror_native (errsv), errsv); goto done; } - if (sdp_set_notify (context->sdp_session, sdp_search_completed_cb, context) < 0) { + if (sdp_set_notify (context->cdat->sdp_session, _connect_sdp_search_cb, context) < 0) { /* Should not be reached, only can fail if we passed bad sdp_session. */ error = g_error_new (NM_BT_ERROR, NM_BT_ERROR_DUN_CONNECT_FAILED, - "Could not request Service Discovery notification"); + "could not set Service Discovery notification"); goto done; } @@ -273,124 +598,261 @@ sdp_connect_watch (GIOChannel *channel, GIOCondition condition, gpointer user_da attr = SDP_ATTR_PROTO_DESC_LIST; attrs = sdp_list_append (NULL, &attr); - if (!sdp_service_search_attr_async (context->sdp_session, search, SDP_ATTR_REQ_INDIVIDUAL, attrs)) { - /* Set callback responsible for update the internal SDP transaction */ - context->sdp_watch_id = g_io_add_watch (channel, - G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL, - sdp_search_process_cb, - context); - } else { - err = sdp_get_error (context->sdp_session); - error = g_error_new (NM_BT_ERROR, - NM_BT_ERROR_DUN_CONNECT_FAILED, - "Error starting Service Discovery: (%d) %s", - err, nm_strerror_native (err)); - } + r = sdp_service_search_attr_async (context->cdat->sdp_session, + search, + SDP_ATTR_REQ_INDIVIDUAL, + attrs); sdp_list_free (attrs, NULL); sdp_list_free (search, NULL); -done: - if (error) { - context->callback (context, NULL, error, context->user_data); - sdp_search_cleanup (context); + if (r < 0) { + errsv = nm_errno_native (sdp_get_error (context->cdat->sdp_session)); + error = g_error_new (NM_BT_ERROR, + NM_BT_ERROR_DUN_CONNECT_FAILED, + "error starting Service Discovery: %s (%d)", + nm_strerror_native (errsv), errsv); + goto done; } + /* Set callback responsible for update the internal SDP transaction */ + context->cdat->source_id = g_io_add_watch (io_channel, + G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL, + _connect_sdp_search_io_cb, + context); + +done: + if (error) + _context_invoke_callback_fail_and_free (context, error); return G_SOURCE_REMOVE; } -NMBluez5DunContext * -nm_bluez5_dun_new (const char *adapter, - const char *remote) +/*****************************************************************************/ +static void +_connect_cancelled_cb (GCancellable *cancellable, + NMBluez5DunContext *context) { - NMBluez5DunContext *context; - - context = g_slice_new0 (NMBluez5DunContext); - str2ba (adapter, &context->src); - str2ba (remote, &context->dst); - context->src_str = g_strdup (adapter); - context->dst_str = g_strdup (remote); - context->rfcomm_channel = -1; - context->rfcomm_id = -1; - context->rfcomm_fd = -1; - return context; + gs_free_error GError *error = NULL; + + if (!g_cancellable_set_error_if_cancelled (cancellable, &error)) + g_return_if_reached (); + + _context_invoke_callback_fail_and_free (context, error); } -void -nm_bluez5_dun_connect (NMBluez5DunContext *context, - NMBluez5DunFunc callback, - gpointer user_data) +static gboolean +_connect_sdp_session_start (NMBluez5DunContext *context, + GError **error) { - GIOChannel *channel; + nm_auto_unref_io_channel GIOChannel *io_channel = NULL; - context->callback = callback; - context->user_data = user_data; + nm_assert (context->cdat); - if (context->rfcomm_channel != -1) { - nm_log_dbg (LOGD_BT, "(%s): channel number on device %s cached: %d", - context->src_str, context->dst_str, context->rfcomm_channel); - /* FIXME: don't invoke the callback synchronously. */ - dun_connect (context); - return; + nm_clear_g_source (&context->cdat->source_id); + nm_clear_pointer (&context->cdat->sdp_session, sdp_close); + + context->cdat->sdp_session = sdp_connect (&context->src, &context->dst, SDP_NON_BLOCKING); + if (!context->cdat->sdp_session) { + int errsv = nm_errno_native (errno); + + g_set_error (error, NM_BT_ERROR, NM_BT_ERROR_DUN_CONNECT_FAILED, + "failed to connect to the SDP server: %s (%d)", + nm_strerror_native (errsv), errsv); + return FALSE; } - nm_log_dbg (LOGD_BT, "(%s): starting channel number discovery for device %s", - context->src_str, context->dst_str); + io_channel = g_io_channel_unix_new (sdp_get_socket (context->cdat->sdp_session)); + context->cdat->source_id = g_io_add_watch (io_channel, + G_IO_OUT | G_IO_HUP | G_IO_ERR | G_IO_NVAL, + _connect_sdp_io_cb, + context); + return TRUE; +} - context->sdp_session = sdp_connect (&context->src, &context->dst, SDP_NON_BLOCKING); - if (!context->sdp_session) { - int err = errno; - GError *error; +/*****************************************************************************/ + +gboolean +nm_bluez5_dun_connect (const char *adapter, + const char *remote, + GCancellable *cancellable, + NMBluez5DunConnectCb callback, + gpointer callback_user_data, + NMBluez5DunNotifyTtyHangupCb notify_tty_hangup_cb, + gpointer notify_tty_hangup_user_data, + GError **error) +{ + nm_auto_free_context NMBluez5DunContext *context = NULL; + ConnectData *cdat; + gsize src_l; + gsize dst_l; + + g_return_val_if_fail (adapter, FALSE); + g_return_val_if_fail (remote, FALSE); + g_return_val_if_fail (G_IS_CANCELLABLE (cancellable), FALSE); + g_return_val_if_fail (callback, FALSE); + g_return_val_if_fail (notify_tty_hangup_cb, FALSE); + g_return_val_if_fail (!error || !*error, FALSE); + nm_assert (!g_cancellable_is_cancelled (cancellable)); + + src_l = strlen (adapter) + 1; + dst_l = strlen (remote) + 1; + + cdat = g_slice_new (ConnectData); + *cdat = (ConnectData) { + .callback = callback, + .callback_user_data = callback_user_data, + .cancellable = g_object_ref (cancellable), + .sdp_session_try_count = 5, + }; - error = g_error_new (NM_BT_ERROR, NM_BT_ERROR_DUN_CONNECT_FAILED, - "Failed to connect to the SDP server: (%d) %s", - err, nm_strerror_native (err)); - /* FIXME: don't invoke the callback synchronously. */ - context->callback (context, NULL, error, context->user_data); - return; + context = g_malloc (sizeof (NMBluez5DunContext) + src_l + dst_l); + *context = (NMBluez5DunContext) { + .cdat = cdat, + .notify_tty_hangup_cb = notify_tty_hangup_cb, + .notify_tty_hangup_user_data = notify_tty_hangup_user_data, + .rfcomm_tty_no = -1, + .rfcomm_sock_fd = -1, + .rfcomm_tty_fd = -1, + .rfcomm_channel = -1, + }; + memcpy (&context->src_str[0], adapter, src_l); + context->dst_str = &context->src_str[src_l]; + memcpy ((char *) context->dst_str, remote, dst_l); + + if (str2ba (adapter, &context->src) < 0) { + g_set_error (error, NM_BT_ERROR, NM_BT_ERROR_DUN_CONNECT_FAILED, + "invalid source"); + return FALSE; } - /* FIXME(shutdown): make connect cancellable. */ - channel = g_io_channel_unix_new (sdp_get_socket (context->sdp_session)); - context->sdp_watch_id = g_io_add_watch (channel, - G_IO_OUT | G_IO_HUP | G_IO_ERR | G_IO_NVAL, - sdp_connect_watch, - context); - g_io_channel_unref (channel); + if (str2ba (remote, &context->dst) < 0) { + g_set_error (error, NM_BT_ERROR, NM_BT_ERROR_DUN_CONNECT_FAILED, + "invalid remote"); + return FALSE; + } + + context->cdat->cancelled_id = g_signal_connect (context->cdat->cancellable, + "cancelled", + G_CALLBACK (_connect_cancelled_cb), + context); + + if (!_connect_sdp_session_start (context, error)) + return FALSE; + + _LOGD (context, "starting channel number discovery for device %s", + context->dst_str); + + g_steal_pointer (&context); + return TRUE; } -/* Only clean up connection-related stuff to allow reconnect */ +/*****************************************************************************/ + void -nm_bluez5_dun_cleanup (NMBluez5DunContext *context) +nm_bluez5_dun_disconnect (NMBluez5DunContext *context) { - g_return_if_fail (context != NULL); + nm_assert (context); + nm_assert (!context->cdat); - sdp_search_cleanup (context); + _LOGD (context, "disconnecting DUN connection"); - if (context->rfcomm_fd >= 0) { - if (context->rfcomm_id >= 0) { - struct rfcomm_dev_req req = { 0 }; + _context_free (context); +} - req.dev_id = context->rfcomm_id; - (void) ioctl (context->rfcomm_fd, RFCOMMRELEASEDEV, &req); - context->rfcomm_id = -1; - } - nm_close (context->rfcomm_fd); - context->rfcomm_fd = -1; - } +/*****************************************************************************/ - nm_close (context->rfcomm_tty_fd); - context->rfcomm_tty_fd = -1; +static void +_context_cleanup_connect_data (NMBluez5DunContext *context) +{ + ConnectData *cdat; + + cdat = g_steal_pointer (&context->cdat); + if (!cdat) + return; + + nm_clear_g_signal_handler (cdat->cancellable, &cdat->cancelled_id); + + nm_clear_g_source (&cdat->source_id); + + nm_clear_pointer (&cdat->sdp_session, sdp_close); + + g_clear_object (&cdat->cancellable); + + g_clear_error (&cdat->rfcomm_sdp_search_error); + + nm_g_slice_free (cdat); } -void -nm_bluez5_dun_free (NMBluez5DunContext *context) +static void +_context_invoke_callback (NMBluez5DunContext *context, + GError *error) +{ + NMBluez5DunConnectCb callback; + gpointer callback_user_data; + + nm_assert (context); + nm_assert (context->cdat); + nm_assert (context->cdat->callback); + nm_assert (error || context->rfcomm_tty_path); + + if (!error) + _LOGD (context, "connected via \"%s\"", context->rfcomm_tty_path); + else if (nm_utils_error_is_cancelled (error, FALSE)) + _LOGD (context, "cancelled"); + else + _LOGD (context, "failed to connect: %s", error->message); + + callback = context->cdat->callback; + callback_user_data = context->cdat->callback_user_data; + + _context_cleanup_connect_data (context); + + callback (error ? NULL : context, + error ? NULL : context->rfcomm_tty_path, + error, + callback_user_data); +} + +static void +_context_invoke_callback_success (NMBluez5DunContext *context) { - g_return_if_fail (context != NULL); + nm_assert (context->rfcomm_tty_path); + _context_invoke_callback (context, NULL); +} + +static void +_context_invoke_callback_fail_and_free (NMBluez5DunContext *context, + GError *error) +{ + nm_assert (error); + _context_invoke_callback (context, error); + _context_free (context); +} + +static void +_context_free (NMBluez5DunContext *context) +{ + nm_assert (context); + + _context_cleanup_connect_data (context); + + nm_clear_g_source (&context->rfcomm_tty_poll_id); + + if (context->rfcomm_sock_fd >= 0) { + if (context->rfcomm_tty_no >= 0) { + struct rfcomm_dev_req req; + + memset (&req, 0, sizeof (struct rfcomm_dev_req)); + req.dev_id = context->rfcomm_tty_no; + context->rfcomm_tty_no = -1; + (void) ioctl (context->rfcomm_sock_fd, RFCOMMRELEASEDEV, &req); + } + nm_close (nm_steal_fd (&context->rfcomm_sock_fd)); + } - nm_bluez5_dun_cleanup (context); - g_clear_pointer (&context->src_str, g_free); - g_clear_pointer (&context->dst_str, g_free); - g_slice_free (NMBluez5DunContext, context); + if (context->rfcomm_tty_fd >= 0) + nm_close (nm_steal_fd (&context->rfcomm_tty_fd)); + nm_clear_g_free (&context->rfcomm_tty_path); + g_free (context); } diff --git a/src/devices/bluetooth/nm-bluez5-dun.h b/src/devices/bluetooth/nm-bluez5-dun.h index d09046fa3d..6142eac26c 100644 --- a/src/devices/bluetooth/nm-bluez5-dun.h +++ b/src/devices/bluetooth/nm-bluez5-dun.h @@ -4,27 +4,36 @@ * Copyright (C) 2014 Red Hat, Inc. */ -#ifndef _NM_BLUEZ5_UTILS_H_ -#define _NM_BLUEZ5_UTILS_H_ +#ifndef __NM_BLUEZ5_DUN_H__ +#define __NM_BLUEZ5_DUN_H__ typedef struct _NMBluez5DunContext NMBluez5DunContext; -typedef void (*NMBluez5DunFunc) (NMBluez5DunContext *context, - const char *rfcomm_dev, - GError *error, - gpointer user_data); +#if WITH_BLUEZ5_DUN -NMBluez5DunContext *nm_bluez5_dun_new (const char *adapter, - const char *remote); +typedef void (*NMBluez5DunConnectCb) (NMBluez5DunContext *context, + const char *rfcomm_dev, + GError *error, + gpointer user_data); -void nm_bluez5_dun_connect (NMBluez5DunContext *context, - NMBluez5DunFunc callback, - gpointer user_data); +typedef void (*NMBluez5DunNotifyTtyHangupCb) (NMBluez5DunContext *context, + gpointer user_data); -/* Clean up connection resources */ -void nm_bluez5_dun_cleanup (NMBluez5DunContext *context); +gboolean nm_bluez5_dun_connect (const char *adapter, + const char *remote, + GCancellable *cancellable, + NMBluez5DunConnectCb callback, + gpointer callback_user_data, + NMBluez5DunNotifyTtyHangupCb notify_tty_hangup_cb, + gpointer notify_tty_hangup_user_data, + GError **error); -/* Clean up and dispose all resources */ -void nm_bluez5_dun_free (NMBluez5DunContext *context); +void nm_bluez5_dun_disconnect (NMBluez5DunContext *context); -#endif /* _NM_BLUEZ5_UTILS_H_ */ +const char *nm_bluez5_dun_context_get_adapter (const NMBluez5DunContext *context); +const char *nm_bluez5_dun_context_get_remote (const NMBluez5DunContext *context); +const char *nm_bluez5_dun_context_get_rfcomm_dev (const NMBluez5DunContext *context); + +#endif /* WITH_BLUEZ5_DUN */ + +#endif /* __NM_BLUEZ5_DUN_H__ */ diff --git a/src/devices/bluetooth/nm-bluez5-manager.c b/src/devices/bluetooth/nm-bluez5-manager.c deleted file mode 100644 index 9ede4d709e..0000000000 --- a/src/devices/bluetooth/nm-bluez5-manager.c +++ /dev/null @@ -1,585 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0+ -/* NetworkManager -- Network link manager - * - * Copyright (C) 2007 - 2008 Novell, Inc. - * Copyright (C) 2007 - 2017 Red Hat, Inc. - * Copyright (C) 2013 Intel Corporation. - */ - -#include "nm-default.h" - -#include "nm-bluez5-manager.h" - -#include <signal.h> -#include <stdlib.h> - -#include "nm-core-internal.h" - -#include "nm-std-aux/nm-dbus-compat.h" -#include "c-list/src/c-list.h" -#include "nm-bluez-device.h" -#include "nm-bluez-common.h" -#include "devices/nm-device-bridge.h" -#include "settings/nm-settings.h" - -/*****************************************************************************/ - -enum { - BDADDR_ADDED, - NETWORK_SERVER_ADDED, - LAST_SIGNAL, -}; - -static guint signals[LAST_SIGNAL] = { 0 }; - -typedef struct { - NMSettings *settings; - - GDBusProxy *proxy; - - GHashTable *devices; - - CList network_servers; -} NMBluez5ManagerPrivate; - -struct _NMBluez5Manager { - GObject parent; - NMBtVTableNetworkServer network_server_vtable; - NMBluez5ManagerPrivate _priv; -}; - -struct _NMBluez5ManagerClass { - GObjectClass parent; -}; - -G_DEFINE_TYPE (NMBluez5Manager, nm_bluez5_manager, G_TYPE_OBJECT) - -#define NM_BLUEZ5_MANAGER_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMBluez5Manager, NM_IS_BLUEZ5_MANAGER) - -#define NM_BLUEZ5_MANAGER_GET_NETWORK_SERVER_VTABLE(self) (&(self)->network_server_vtable) - -#define NETWORK_SERVER_VTABLE_GET_NM_BLUEZ5_MANAGER(vtable) \ - NM_BLUEZ5_MANAGER(((char *)(vtable)) - offsetof (struct _NMBluez5Manager, network_server_vtable)) - -/*****************************************************************************/ - -#define _NMLOG_DOMAIN LOGD_BT -#define _NMLOG(level, ...) __NMLOG_DEFAULT (level, _NMLOG_DOMAIN, "bluez5", __VA_ARGS__) - -/*****************************************************************************/ - -static void device_initialized (NMBluezDevice *device, gboolean success, NMBluez5Manager *self); -static void device_usable (NMBluezDevice *device, GParamSpec *pspec, NMBluez5Manager *self); - -/*****************************************************************************/ - -typedef struct { - char *path; - char *addr; - NMDevice *device; - CList lst_ns; -} NetworkServer; - -static NetworkServer * -_find_network_server (NMBluez5Manager *self, const char *path, NMDevice *device) -{ - NMBluez5ManagerPrivate *priv = NM_BLUEZ5_MANAGER_GET_PRIVATE (self); - NetworkServer *network_server; - - nm_assert (path || NM_IS_DEVICE (device)); - - c_list_for_each_entry (network_server, &priv->network_servers, lst_ns) { - if (path && !nm_streq (network_server->path, path)) - continue; - if (device && network_server->device != device) - continue; - return network_server; - } - return NULL; -} - -static NetworkServer * -_find_network_server_for_addr (NMBluez5Manager *self, const char *addr) -{ - NMBluez5ManagerPrivate *priv = NM_BLUEZ5_MANAGER_GET_PRIVATE (self); - NetworkServer *network_server; - - c_list_for_each_entry (network_server, &priv->network_servers, lst_ns) { - /* The address lookups need a server not assigned to a device - * and tolerate an empty address as a wildcard for "any". */ - if ( !network_server->device - && (!addr || nm_streq (network_server->addr, addr))) - return network_server; - } - return NULL; -} - -static void -_network_server_unregister (NMBluez5Manager *self, NetworkServer *network_server) -{ - NMBluez5ManagerPrivate *priv = NM_BLUEZ5_MANAGER_GET_PRIVATE (self); - - if (!network_server->device) { - /* Not connected. */ - return; - } - - _LOGI ("NAP: unregistering %s from %s", - nm_device_get_iface (network_server->device), - network_server->addr); - - g_dbus_connection_call (g_dbus_proxy_get_connection (priv->proxy), - NM_BLUEZ_SERVICE, - network_server->path, - NM_BLUEZ5_NETWORK_SERVER_INTERFACE, - "Unregister", - g_variant_new ("(s)", BLUETOOTH_CONNECT_NAP), - NULL, - G_DBUS_CALL_FLAGS_NONE, - -1, NULL, NULL, NULL); - - g_clear_object (&network_server->device); -} - -static void -_network_server_free (NMBluez5Manager *self, NetworkServer *network_server) -{ - _network_server_unregister (self, network_server); - c_list_unlink_stale (&network_server->lst_ns); - g_free (network_server->path); - g_free (network_server->addr); - g_slice_free (NetworkServer, network_server); -} - -static gboolean -network_server_is_available (const NMBtVTableNetworkServer *vtable, - const char *addr) -{ - NMBluez5Manager *self = NETWORK_SERVER_VTABLE_GET_NM_BLUEZ5_MANAGER (vtable); - - return !!_find_network_server_for_addr (self, addr); -} - -static gboolean -network_server_register_bridge (const NMBtVTableNetworkServer *vtable, - const char *addr, - NMDevice *device) -{ - NMBluez5Manager *self = NETWORK_SERVER_VTABLE_GET_NM_BLUEZ5_MANAGER (vtable); - NMBluez5ManagerPrivate *priv = NM_BLUEZ5_MANAGER_GET_PRIVATE (self); - NetworkServer *network_server = _find_network_server_for_addr (self, addr); - - nm_assert (NM_IS_DEVICE (device)); - nm_assert (!_find_network_server (self, NULL, device)); - - if (!network_server) { - /* The device checked that a network server is available, before - * starting the activation, but for some reason it no longer is. - * Indicate that the activation should not proceed. */ - _LOGI ("NAP: %s is not available for %s", addr, nm_device_get_iface (device)); - return FALSE; - } - - _LOGI ("NAP: registering %s on %s", nm_device_get_iface (device), network_server->addr); - - g_dbus_connection_call (g_dbus_proxy_get_connection (priv->proxy), - NM_BLUEZ_SERVICE, - network_server->path, - NM_BLUEZ5_NETWORK_SERVER_INTERFACE, - "Register", - g_variant_new ("(ss)", BLUETOOTH_CONNECT_NAP, nm_device_get_iface (device)), - NULL, - G_DBUS_CALL_FLAGS_NONE, - -1, NULL, NULL, NULL); - - network_server->device = g_object_ref (device); - - return TRUE; -} - -static gboolean -network_server_unregister_bridge (const NMBtVTableNetworkServer *vtable, - NMDevice *device) -{ - NMBluez5Manager *self = NETWORK_SERVER_VTABLE_GET_NM_BLUEZ5_MANAGER (vtable); - NetworkServer *network_server = _find_network_server (self, NULL, device); - - if (network_server) - _network_server_unregister (self, network_server); - - return TRUE; -} - -static void -network_server_removed (GDBusProxy *proxy, const char *path, NMBluez5Manager *self) -{ - NetworkServer *network_server; - - network_server = _find_network_server (self, path, NULL); - if (!network_server) - return; - - if (network_server->device) { - nm_device_queue_state (network_server->device, NM_DEVICE_STATE_DISCONNECTED, - NM_DEVICE_STATE_REASON_BT_FAILED); - } - _LOGI ("NAP: removed interface %s", network_server->addr); - _network_server_free (self, network_server); -} - -static void -network_server_added (GDBusProxy *proxy, const char *path, const char *addr, NMBluez5Manager *self) -{ - NMBluez5ManagerPrivate *priv = NM_BLUEZ5_MANAGER_GET_PRIVATE (self); - NetworkServer *network_server; - - /* If BlueZ messes up and announces a single network server twice, - * make sure we get rid of the older instance first. */ - network_server_removed (proxy, path, self); - - network_server = g_slice_new0 (NetworkServer); - network_server->path = g_strdup (path); - network_server->addr = g_strdup (addr); - c_list_link_before (&priv->network_servers, &network_server->lst_ns); - - _LOGI ("NAP: added interface %s", addr); - - g_signal_emit (self, signals[NETWORK_SERVER_ADDED], 0); -} - -/*****************************************************************************/ - -static void -emit_bdaddr_added (NMBluez5Manager *self, NMBluezDevice *device) -{ - g_signal_emit (self, signals[BDADDR_ADDED], 0, - device, - nm_bluez_device_get_address (device), - nm_bluez_device_get_name (device), - nm_bluez_device_get_path (device), - nm_bluez_device_get_capabilities (device)); -} - -void -nm_bluez5_manager_query_devices (NMBluez5Manager *self) -{ - NMBluez5ManagerPrivate *priv = NM_BLUEZ5_MANAGER_GET_PRIVATE (self); - NMBluezDevice *device; - GHashTableIter iter; - - g_hash_table_iter_init (&iter, priv->devices); - while (g_hash_table_iter_next (&iter, NULL, (gpointer) &device)) { - if (nm_bluez_device_get_usable (device)) - emit_bdaddr_added (self, device); - } -} - -static void -remove_device (NMBluez5Manager *self, NMBluezDevice *device) -{ - g_signal_handlers_disconnect_by_func (device, G_CALLBACK (device_initialized), self); - g_signal_handlers_disconnect_by_func (device, G_CALLBACK (device_usable), self); - if (nm_bluez_device_get_usable (device)) - g_signal_emit_by_name (device, NM_BLUEZ_DEVICE_REMOVED); -} - -static void -remove_all_devices (NMBluez5Manager *self) -{ - GHashTableIter iter; - NMBluezDevice *device; - NMBluez5ManagerPrivate *priv = NM_BLUEZ5_MANAGER_GET_PRIVATE (self); - - g_hash_table_iter_init (&iter, priv->devices); - while (g_hash_table_iter_next (&iter, NULL, (gpointer) &device)) { - g_hash_table_iter_steal (&iter); - remove_device (self, device); - g_object_unref (device); - } -} - -static void -device_usable (NMBluezDevice *device, GParamSpec *pspec, NMBluez5Manager *self) -{ - gboolean usable = nm_bluez_device_get_usable (device); - - _LOGD ("(%s): bluez device now %s", - nm_bluez_device_get_path (device), - usable ? "usable" : "unusable"); - - if (usable) { - _LOGD ("(%s): bluez device address %s", - nm_bluez_device_get_path (device), - nm_bluez_device_get_address (device)); - emit_bdaddr_added (self, device); - } else - g_signal_emit_by_name (device, NM_BLUEZ_DEVICE_REMOVED); -} - -static void -device_initialized (NMBluezDevice *device, gboolean success, NMBluez5Manager *self) -{ - NMBluez5ManagerPrivate *priv = NM_BLUEZ5_MANAGER_GET_PRIVATE (self); - - _LOGD ("(%s): bluez device %s", - nm_bluez_device_get_path (device), - success ? "initialized" : "failed to initialize"); - if (!success) - g_hash_table_remove (priv->devices, nm_bluez_device_get_path (device)); -} - -static void -device_added (GDBusProxy *proxy, const char *path, NMBluez5Manager *self) -{ - NMBluez5ManagerPrivate *priv = NM_BLUEZ5_MANAGER_GET_PRIVATE (self); - NMBluezDevice *device; - - device = nm_bluez_device_new (g_dbus_proxy_get_connection (proxy), path, priv->settings); - g_signal_connect (device, NM_BLUEZ_DEVICE_INITIALIZED, G_CALLBACK (device_initialized), self); - g_signal_connect (device, "notify::" NM_BLUEZ_DEVICE_USABLE, G_CALLBACK (device_usable), self); - g_hash_table_insert (priv->devices, (gpointer) nm_bluez_device_get_path (device), device); - - _LOGD ("(%s): new bluez device found", path); -} - -static void -device_removed (GDBusProxy *proxy, const char *path, NMBluez5Manager *self) -{ - NMBluez5ManagerPrivate *priv = NM_BLUEZ5_MANAGER_GET_PRIVATE (self); - NMBluezDevice *device; - - _LOGD ("(%s): bluez device removed", path); - - device = g_hash_table_lookup (priv->devices, path); - if (device) { - g_hash_table_steal (priv->devices, nm_bluez_device_get_path (device)); - remove_device (NM_BLUEZ5_MANAGER (self), device); - g_object_unref (device); - } -} - -static void -object_manager_interfaces_added (GDBusProxy *proxy, - const char *path, - GVariant *dict, - NMBluez5Manager *self) -{ - if (g_variant_lookup (dict, NM_BLUEZ5_DEVICE_INTERFACE, "a{sv}", NULL)) - device_added (proxy, path, self); - if (g_variant_lookup (dict, NM_BLUEZ5_NETWORK_SERVER_INTERFACE, "a{sv}", NULL)) { - gs_unref_variant GVariant *adapter = g_variant_lookup_value (dict, NM_BLUEZ5_ADAPTER_INTERFACE, G_VARIANT_TYPE_DICTIONARY); - const char *address; - - if ( adapter - && g_variant_lookup (adapter, "Address", "&s", &address)) - network_server_added (proxy, path, address, self); - } -} - -static void -object_manager_interfaces_removed (GDBusProxy *proxy, - const char *path, - const char **ifaces, - NMBluez5Manager *self) -{ - if (ifaces && g_strv_contains (ifaces, NM_BLUEZ5_DEVICE_INTERFACE)) - device_removed (proxy, path, self); - if (ifaces && g_strv_contains (ifaces, NM_BLUEZ5_NETWORK_SERVER_INTERFACE)) - network_server_removed (proxy, path, self); -} - -static void -get_managed_objects_cb (GDBusProxy *proxy, - GAsyncResult *res, - NMBluez5Manager *self) -{ - gs_unref_variant GVariant *variant0 = NULL; - GVariant *variant, *ifaces; - GVariantIter i; - GError *error = NULL; - const char *path; - - variant = _nm_dbus_proxy_call_finish (proxy, res, - G_VARIANT_TYPE ("(a{oa{sa{sv}}})"), - &error); - if (!variant) { - if (g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_METHOD)) - _LOGW ("Couldn't get managed objects: not running Bluez5?"); - else { - g_dbus_error_strip_remote_error (error); - _LOGW ("Couldn't get managed objects: %s", error->message); - } - g_clear_error (&error); - return; - } - variant0 = g_variant_get_child_value (variant, 0); - g_variant_iter_init (&i, variant0); - while ((g_variant_iter_next (&i, "{&o*}", &path, &ifaces))) { - object_manager_interfaces_added (proxy, path, ifaces, self); - g_variant_unref (ifaces); - } - - g_variant_unref (variant); -} - -static void name_owner_changed_cb (GObject *object, GParamSpec *pspec, gpointer user_data); - -static void -on_proxy_acquired (GObject *object, - GAsyncResult *res, - NMBluez5Manager *self) -{ - NMBluez5ManagerPrivate *priv = NM_BLUEZ5_MANAGER_GET_PRIVATE (self); - GError *error = NULL; - - priv->proxy = g_dbus_proxy_new_for_bus_finish (res, &error); - - if (!priv->proxy) { - _LOGW ("Couldn't acquire object manager proxy: %s", error->message); - g_clear_error (&error); - return; - } - - g_signal_connect (priv->proxy, "notify::g-name-owner", - G_CALLBACK (name_owner_changed_cb), self); - - /* Get already managed devices. */ - g_dbus_proxy_call (priv->proxy, "GetManagedObjects", - NULL, - G_DBUS_CALL_FLAGS_NONE, - -1, - NULL, - (GAsyncReadyCallback) get_managed_objects_cb, - self); - - _nm_dbus_signal_connect (priv->proxy, "InterfacesAdded", G_VARIANT_TYPE ("(oa{sa{sv}})"), - G_CALLBACK (object_manager_interfaces_added), self); - _nm_dbus_signal_connect (priv->proxy, "InterfacesRemoved", G_VARIANT_TYPE ("(oas)"), - G_CALLBACK (object_manager_interfaces_removed), self); -} - -static void -bluez_connect (NMBluez5Manager *self) -{ - NMBluez5ManagerPrivate *priv = NM_BLUEZ5_MANAGER_GET_PRIVATE (self); - - g_return_if_fail (priv->proxy == NULL); - - g_dbus_proxy_new_for_bus (G_BUS_TYPE_SYSTEM, - G_DBUS_PROXY_FLAGS_NONE, - NULL, - NM_BLUEZ_SERVICE, - NM_BLUEZ_MANAGER_PATH, - DBUS_INTERFACE_OBJECT_MANAGER, - NULL, - (GAsyncReadyCallback) on_proxy_acquired, - self); -} - -static void -name_owner_changed_cb (GObject *object, - GParamSpec *pspec, - gpointer user_data) -{ - NMBluez5Manager *self = NM_BLUEZ5_MANAGER (user_data); - NMBluez5ManagerPrivate *priv = NM_BLUEZ5_MANAGER_GET_PRIVATE (self); - char *owner; - - if (priv->devices) { - owner = g_dbus_proxy_get_name_owner (priv->proxy); - if (!owner) - remove_all_devices (self); - g_free (owner); - } -} - -/*****************************************************************************/ - -static void -nm_bluez5_manager_init (NMBluez5Manager *self) -{ - NMBluez5ManagerPrivate *priv = NM_BLUEZ5_MANAGER_GET_PRIVATE (self); - NMBtVTableNetworkServer *network_server_vtable = NM_BLUEZ5_MANAGER_GET_NETWORK_SERVER_VTABLE (self); - - bluez_connect (self); - - priv->devices = g_hash_table_new_full (nm_str_hash, g_str_equal, - NULL, g_object_unref); - - c_list_init (&priv->network_servers); - - nm_assert (!nm_bt_vtable_network_server); - network_server_vtable->is_available = network_server_is_available; - network_server_vtable->register_bridge = network_server_register_bridge; - network_server_vtable->unregister_bridge = network_server_unregister_bridge; - nm_bt_vtable_network_server = network_server_vtable; -} - -NMBluez5Manager * -nm_bluez5_manager_new (NMSettings *settings) -{ - NMBluez5Manager *instance = NULL; - - g_return_val_if_fail (NM_IS_SETTINGS (settings), NULL); - - instance = g_object_new (NM_TYPE_BLUEZ5_MANAGER, NULL); - NM_BLUEZ5_MANAGER_GET_PRIVATE (instance)->settings = g_object_ref (settings); - return instance; -} - -static void -dispose (GObject *object) -{ - NMBluez5Manager *self = NM_BLUEZ5_MANAGER (object); - NMBluez5ManagerPrivate *priv = NM_BLUEZ5_MANAGER_GET_PRIVATE (self); - CList *iter, *safe; - - c_list_for_each_safe (iter, safe, &priv->network_servers) - _network_server_free (self, c_list_entry (iter, NetworkServer, lst_ns)); - - if (priv->proxy) { - g_signal_handlers_disconnect_by_func (priv->proxy, G_CALLBACK (name_owner_changed_cb), self); - g_clear_object (&priv->proxy); - } - - g_hash_table_remove_all (priv->devices); - - G_OBJECT_CLASS (nm_bluez5_manager_parent_class)->dispose (object); -} - -static void -finalize (GObject *object) -{ - NMBluez5Manager *self = NM_BLUEZ5_MANAGER (object); - NMBluez5ManagerPrivate *priv = NM_BLUEZ5_MANAGER_GET_PRIVATE (self); - - g_hash_table_destroy (priv->devices); - - G_OBJECT_CLASS (nm_bluez5_manager_parent_class)->finalize (object); - - g_object_unref (priv->settings); -} - -static void -nm_bluez5_manager_class_init (NMBluez5ManagerClass *klass) -{ - GObjectClass *object_class = G_OBJECT_CLASS (klass); - - object_class->dispose = dispose; - object_class->finalize = finalize; - - signals[BDADDR_ADDED] = - g_signal_new (NM_BLUEZ_MANAGER_BDADDR_ADDED, - G_OBJECT_CLASS_TYPE (object_class), - G_SIGNAL_RUN_FIRST, - 0, NULL, NULL, NULL, - G_TYPE_NONE, 5, G_TYPE_OBJECT, G_TYPE_STRING, - G_TYPE_STRING, G_TYPE_STRING, G_TYPE_UINT); - - signals[NETWORK_SERVER_ADDED] = - g_signal_new (NM_BLUEZ_MANAGER_NETWORK_SERVER_ADDED, - G_OBJECT_CLASS_TYPE (object_class), - G_SIGNAL_RUN_FIRST, - 0, NULL, NULL, NULL, - G_TYPE_NONE, 0); -} diff --git a/src/devices/bluetooth/nm-bluez5-manager.h b/src/devices/bluetooth/nm-bluez5-manager.h deleted file mode 100644 index a3f81a8ce9..0000000000 --- a/src/devices/bluetooth/nm-bluez5-manager.h +++ /dev/null @@ -1,27 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0+ -/* NetworkManager -- Network link manager - * - * Copyright (C) 2007 - 2008 Novell, Inc. - * Copyright (C) 2007 - 2013 Red Hat, Inc. - */ - -#ifndef __NETWORKMANAGER_BLUEZ5_MANAGER_H__ -#define __NETWORKMANAGER_BLUEZ5_MANAGER_H__ - -#define NM_TYPE_BLUEZ5_MANAGER (nm_bluez5_manager_get_type ()) -#define NM_BLUEZ5_MANAGER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_BLUEZ5_MANAGER, NMBluez5Manager)) -#define NM_BLUEZ5_MANAGER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), NM_TYPE_BLUEZ5_MANAGER, NMBluez5ManagerClass)) -#define NM_IS_BLUEZ5_MANAGER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_BLUEZ5_MANAGER)) -#define NM_IS_BLUEZ5_MANAGER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NM_TYPE_BLUEZ5_MANAGER)) -#define NM_BLUEZ5_MANAGER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), NM_TYPE_BLUEZ5_MANAGER, NMBluez5ManagerClass)) - -typedef struct _NMBluez5Manager NMBluez5Manager; -typedef struct _NMBluez5ManagerClass NMBluez5ManagerClass; - -GType nm_bluez5_manager_get_type (void); - -NMBluez5Manager *nm_bluez5_manager_new (NMSettings *settings); - -void nm_bluez5_manager_query_devices (NMBluez5Manager *manager); - -#endif /* __NETWORKMANAGER_BLUEZ5_MANAGER_H__ */ diff --git a/src/devices/bluetooth/nm-device-bt.c b/src/devices/bluetooth/nm-device-bt.c index e904fd44ea..61a7759e11 100644 --- a/src/devices/bluetooth/nm-device-bt.c +++ b/src/devices/bluetooth/nm-device-bt.c @@ -10,8 +10,9 @@ #include <stdio.h> +#include "nm-core-internal.h" #include "nm-bluez-common.h" -#include "nm-bluez-device.h" +#include "nm-bluez-manager.h" #include "devices/nm-device-private.h" #include "ppp/nm-ppp-manager.h" #include "nm-setting-connection.h" @@ -35,10 +36,12 @@ _LOG_DECLARE_SELF(NMDeviceBt); /*****************************************************************************/ -NM_GOBJECT_PROPERTIES_DEFINE_BASE ( - PROP_BT_NAME, +NM_GOBJECT_PROPERTIES_DEFINE (NMDeviceBt, + PROP_BT_BDADDR, + PROP_BT_BZ_MGR, PROP_BT_CAPABILITIES, - PROP_BT_DEVICE, + PROP_BT_DBUS_PATH, + PROP_BT_NAME, ); enum { @@ -51,24 +54,38 @@ static guint signals[LAST_SIGNAL] = { 0 }; typedef struct { NMModemManager *modem_manager; - gboolean mm_running; + NMBluezManager *bz_mgr; - NMBluezDevice *bt_device; + char *dbus_path; char *bdaddr; char *name; - guint32 capabilities; - gboolean connected; - gboolean have_iface; + char *connect_rfcomm_iface; + + GSList *connect_modem_candidates; - char *rfcomm_iface; NMModem *modem; - guint timeout_id; - GCancellable *cancellable; + GCancellable *connect_bz_cancellable; + + gulong connect_watch_link_id; + + guint connect_watch_link_idle_id; + + guint connect_wait_modem_id; + + NMBluetoothCapabilities capabilities:6; + + NMBluetoothCapabilities connect_bt_type:6; /* BT type of the current connection */ + + NMDeviceStageState stage1_bt_state:3; + NMDeviceStageState stage1_modem_prepare_state:3; + + bool is_connected:1; + + bool mm_running:1; - guint32 bt_type; /* BT type of the current connection */ } NMDeviceBtPrivate; struct _NMDeviceBt { @@ -82,42 +99,65 @@ struct _NMDeviceBtClass { G_DEFINE_TYPE (NMDeviceBt, nm_device_bt, NM_TYPE_DEVICE) -#define NM_DEVICE_BT_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMDeviceBt, NM_IS_DEVICE_BT) - -/*****************************************************************************/ - -static gboolean modem_stage1 (NMDeviceBt *self, NMModem *modem, NMDeviceStateReason *out_failure_reason); +#define NM_DEVICE_BT_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMDeviceBt, NM_IS_DEVICE_BT, NMDevice) /*****************************************************************************/ -guint32 nm_device_bt_get_capabilities (NMDeviceBt *self) +NMBluetoothCapabilities nm_device_bt_get_capabilities (NMDeviceBt *self) { g_return_val_if_fail (NM_IS_DEVICE_BT (self), NM_BT_CAPABILITY_NONE); return NM_DEVICE_BT_GET_PRIVATE (self)->capabilities; } -static guint32 +static NMBluetoothCapabilities get_connection_bt_type (NMConnection *connection) { NMSettingBluetooth *s_bt; const char *bt_type; s_bt = nm_connection_get_setting_bluetooth (connection); - if (!s_bt) - return NM_BT_CAPABILITY_NONE; - bt_type = nm_setting_bluetooth_get_connection_type (s_bt); - g_assert (bt_type); - - if (!strcmp (bt_type, NM_SETTING_BLUETOOTH_TYPE_DUN)) - return NM_BT_CAPABILITY_DUN; - else if (!strcmp (bt_type, NM_SETTING_BLUETOOTH_TYPE_PANU)) - return NM_BT_CAPABILITY_NAP; + if (s_bt) { + bt_type = nm_setting_bluetooth_get_connection_type (s_bt); + if (bt_type) { + if (nm_streq (bt_type, NM_SETTING_BLUETOOTH_TYPE_DUN)) + return NM_BT_CAPABILITY_DUN; + else if (nm_streq (bt_type, NM_SETTING_BLUETOOTH_TYPE_PANU)) + return NM_BT_CAPABILITY_NAP; + } + } return NM_BT_CAPABILITY_NONE; } +static gboolean +get_connection_bt_type_check (NMDeviceBt *self, + NMConnection *connection, + NMBluetoothCapabilities *out_bt_type, + GError **error) +{ + NMBluetoothCapabilities bt_type; + + bt_type = get_connection_bt_type (connection); + + NM_SET_OUT (out_bt_type, bt_type); + + if (bt_type == NM_BT_CAPABILITY_NONE) { + nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, + "profile is not a PANU/DUN bluetooth type"); + return FALSE; + } + + if (!NM_FLAGS_ALL (NM_DEVICE_BT_GET_PRIVATE (self)->capabilities, bt_type)) { + nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, + "device does not support bluetooth type"); + return FALSE; + } + + return TRUE; +} + static NMDeviceCapabilities get_generic_capabilities (NMDevice *device) { @@ -129,17 +169,24 @@ can_auto_connect (NMDevice *device, NMSettingsConnection *sett_conn, char **specific_object) { - NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE ((NMDeviceBt *) device); - guint32 bt_type; + NMDeviceBt *self = NM_DEVICE_BT (device); + NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE (self); + NMBluetoothCapabilities bt_type; nm_assert (!specific_object || !*specific_object); if (!NM_DEVICE_CLASS (nm_device_bt_parent_class)->can_auto_connect (device, sett_conn, NULL)) return FALSE; + if (!get_connection_bt_type_check (self, + nm_settings_connection_get_connection (sett_conn), + &bt_type, + NULL)) + return FALSE; + /* Can't auto-activate a DUN connection without ModemManager */ - bt_type = get_connection_bt_type (nm_settings_connection_get_connection (sett_conn)); - if (bt_type == NM_BT_CAPABILITY_DUN && priv->mm_running == FALSE) + if ( bt_type == NM_BT_CAPABILITY_DUN + && priv->mm_running == FALSE) return FALSE; return TRUE; @@ -148,20 +195,16 @@ can_auto_connect (NMDevice *device, static gboolean check_connection_compatible (NMDevice *device, NMConnection *connection, GError **error) { - NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE ((NMDeviceBt *) device); + NMDeviceBt *self = NM_DEVICE_BT (device); + NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE (self); NMSettingBluetooth *s_bt; const char *bdaddr; - guint32 bt_type; if (!NM_DEVICE_CLASS (nm_device_bt_parent_class)->check_connection_compatible (device, connection, error)) return FALSE; - bt_type = get_connection_bt_type (connection); - if (!NM_FLAGS_ALL (priv->capabilities, bt_type)) { - nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, - "device does not support bluetooth type of profile"); + if (!get_connection_bt_type_check (self, connection, NULL, error)) return FALSE; - } s_bt = nm_connection_get_setting_bluetooth (connection); @@ -187,18 +230,15 @@ check_connection_available (NMDevice *device, const char *specific_object, GError **error) { - NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE ((NMDeviceBt *) device); - guint32 bt_type; + NMDeviceBt *self = NM_DEVICE_BT (device); + NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE (self); + NMBluetoothCapabilities bt_type; - bt_type = get_connection_bt_type (connection); - if (!(bt_type & priv->capabilities)) { - nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, - "device does not support bluetooth type"); + if (!get_connection_bt_type_check (self, connection, &bt_type, error)) return FALSE; - } - /* DUN connections aren't available without ModemManager */ - if (bt_type == NM_BT_CAPABILITY_DUN && priv->mm_running == FALSE) { + if ( bt_type == NM_BT_CAPABILITY_DUN + && !priv->mm_running) { nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, "ModemManager missing for DUN profile"); return FALSE; @@ -214,11 +254,12 @@ complete_connection (NMDevice *device, NMConnection *const*existing_connections, GError **error) { - NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE ((NMDeviceBt *) device); + NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE (device); NMSettingBluetooth *s_bt; const char *setting_bdaddr; const char *ctype; - gboolean is_dun = FALSE, is_pan = FALSE; + gboolean is_dun = FALSE; + gboolean is_pan = FALSE; NMSettingGsm *s_gsm; NMSettingCdma *s_cdma; NMSettingSerial *s_serial; @@ -261,7 +302,10 @@ complete_connection (NMDevice *device, } /* PAN can't use any DUN-related settings */ - if (s_gsm || s_cdma || s_serial || s_ppp) { + if ( s_gsm + || s_cdma + || s_serial + || s_ppp) { g_set_error_literal (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_SETTING, @@ -291,7 +335,8 @@ complete_connection (NMDevice *device, } /* Need at least a GSM or a CDMA setting */ - if (!s_gsm && !s_cdma) { + if ( !s_gsm + && !s_cdma) { g_set_error_literal (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_SETTING, @@ -428,20 +473,19 @@ static void modem_auth_result (NMModem *modem, GError *error, gpointer user_data) { NMDevice *device = NM_DEVICE (user_data); - NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE ((NMDeviceBt *) device); + NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE (device); + + g_return_if_fail (nm_device_get_state (device) == NM_DEVICE_STATE_NEED_AUTH); if (error) { nm_device_state_changed (device, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_NO_SECRETS); - } else { - NMDeviceStateReason failure_reason = NM_DEVICE_STATE_REASON_NONE; - - /* Otherwise, on success for GSM/CDMA secrets we need to schedule modem stage1 again */ - g_return_if_fail (nm_device_get_state (device) == NM_DEVICE_STATE_NEED_AUTH); - if (!modem_stage1 (NM_DEVICE_BT (device), priv->modem, &failure_reason)) - nm_device_state_changed (device, NM_DEVICE_STATE_FAILED, failure_reason); + return; } + + priv->stage1_modem_prepare_state = NM_DEVICE_STAGE_STATE_INIT; + nm_device_activate_schedule_stage1_device_prepare (device); } static void @@ -450,29 +494,33 @@ modem_prepare_result (NMModem *modem, guint i_reason, gpointer user_data) { - NMDeviceBt *self = NM_DEVICE_BT (user_data); - NMDevice *device = NM_DEVICE (self); + NMDeviceBt *self = user_data; + NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE (self); NMDeviceStateReason reason = i_reason; NMDeviceState state; - state = nm_device_get_state (device); - g_return_if_fail (state == NM_DEVICE_STATE_CONFIG || state == NM_DEVICE_STATE_NEED_AUTH); + state = nm_device_get_state (NM_DEVICE (self)); - if (success) { - nm_modem_act_stage2_config (modem); + g_return_if_fail (NM_IN_SET (state, NM_DEVICE_STATE_PREPARE, + NM_DEVICE_STATE_NEED_AUTH)); - nm_device_activate_schedule_stage3_ip_config_start (device); - } else { + nm_assert (priv->stage1_modem_prepare_state == NM_DEVICE_STAGE_STATE_PENDING); + + if (!success) { if (nm_device_state_reason_check (reason) == NM_DEVICE_STATE_REASON_SIM_PIN_INCORRECT) { /* If the connect failed because the SIM PIN was wrong don't allow * the device to be auto-activated anymore, which would risk locking * the SIM if the incorrect PIN continues to be used. */ - nm_device_autoconnect_blocked_set (device, NM_DEVICE_AUTOCONNECT_BLOCKED_WRONG_PIN); + nm_device_autoconnect_blocked_set (NM_DEVICE (self), NM_DEVICE_AUTOCONNECT_BLOCKED_WRONG_PIN); } - nm_device_state_changed (device, NM_DEVICE_STATE_FAILED, reason); + nm_device_state_changed (NM_DEVICE (self), NM_DEVICE_STATE_FAILED, reason); + return; } + + priv->stage1_modem_prepare_state = NM_DEVICE_STAGE_STATE_COMPLETED; + nm_device_activate_schedule_stage1_device_prepare (NM_DEVICE (self)); } static void @@ -481,7 +529,7 @@ device_state_changed (NMDevice *device, NMDeviceState old_state, NMDeviceStateReason reason) { - NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE ((NMDeviceBt *) device); + NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE (device); if (priv->modem) nm_modem_device_state_changed (priv->modem, new_state, old_state); @@ -490,7 +538,8 @@ device_state_changed (NMDevice *device, * since the device could be both DUN and NAP capable and thus may not * change state (which rechecks available connections) when MM comes and goes. */ - if (priv->mm_running && (priv->capabilities & NM_BT_CAPABILITY_DUN)) + if ( priv->mm_running + && NM_FLAGS_HAS (priv->capabilities, NM_BT_CAPABILITY_DUN)) nm_device_recheck_available_connections (device); } @@ -512,8 +561,10 @@ modem_ip4_config_result (NMModem *modem, nm_device_ip_method_failed (device, AF_INET, NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE); - } else - nm_device_activate_schedule_ip_config_result (device, AF_INET, NM_IP_CONFIG_CAST (config)); + return; + } + + nm_device_activate_schedule_ip_config_result (device, AF_INET, NM_IP_CONFIG_CAST (config)); } static void @@ -529,29 +580,6 @@ ip_ifindex_changed_cb (NMModem *modem, GParamSpec *pspec, gpointer user_data) } } -static gboolean -modem_stage1 (NMDeviceBt *self, NMModem *modem, NMDeviceStateReason *out_failure_reason) -{ - NMActRequest *req; - NMActStageReturn ret; - - req = nm_device_get_act_request (NM_DEVICE (self)); - g_return_val_if_fail (req, FALSE); - - ret = nm_modem_act_stage1_prepare (modem, req, out_failure_reason); - switch (ret) { - case NM_ACT_STAGE_RETURN_POSTPONE: - case NM_ACT_STAGE_RETURN_SUCCESS: - /* Success, wait for the 'prepare-result' signal */ - return TRUE; - case NM_ACT_STAGE_RETURN_FAILURE: - default: - break; - } - - return FALSE; -} - /*****************************************************************************/ static void @@ -576,11 +604,13 @@ modem_state_cb (NMModem *modem, NMDevice *device = NM_DEVICE (user_data); NMDeviceState dev_state = nm_device_get_state (device); - if (new_state <= NM_MODEM_STATE_DISABLING && old_state > NM_MODEM_STATE_DISABLING) { + if ( new_state <= NM_MODEM_STATE_DISABLING + && old_state > NM_MODEM_STATE_DISABLING) { /* Will be called whenever something external to NM disables the * modem directly through ModemManager. */ - if (nm_device_is_activating (device) || dev_state == NM_DEVICE_STATE_ACTIVATED) { + if ( nm_device_is_activating (device) + || dev_state == NM_DEVICE_STATE_ACTIVATED) { nm_device_state_changed (device, NM_DEVICE_STATE_DISCONNECTED, NM_DEVICE_STATE_REASON_USER_REQUESTED); @@ -588,13 +618,15 @@ modem_state_cb (NMModem *modem, } } - if (new_state < NM_MODEM_STATE_CONNECTING && - old_state >= NM_MODEM_STATE_CONNECTING && - dev_state >= NM_DEVICE_STATE_NEED_AUTH && - dev_state <= NM_DEVICE_STATE_ACTIVATED) { + if ( new_state < NM_MODEM_STATE_CONNECTING + && old_state >= NM_MODEM_STATE_CONNECTING + && dev_state >= NM_DEVICE_STATE_NEED_AUTH + && dev_state <= NM_DEVICE_STATE_ACTIVATED) { /* Fail the device if the modem disconnects unexpectedly while the * device is activating/activated. */ - nm_device_state_changed (device, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_MODEM_NO_CARRIER); + nm_device_state_changed (device, + NM_DEVICE_STATE_FAILED, + NM_DEVICE_STATE_REASON_MODEM_NO_CARRIER); return; } } @@ -605,67 +637,56 @@ modem_removed_cb (NMModem *modem, gpointer user_data) NMDeviceBt *self = NM_DEVICE_BT (user_data); NMDeviceState state; - /* Fail the device if the modem was removed while active */ state = nm_device_get_state (NM_DEVICE (self)); - if ( state == NM_DEVICE_STATE_ACTIVATED - || nm_device_is_activating (NM_DEVICE (self))) { + if ( nm_device_is_activating (NM_DEVICE (self)) + || state == NM_DEVICE_STATE_ACTIVATED) { nm_device_state_changed (NM_DEVICE (self), NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_BT_FAILED); - } else - modem_cleanup (self); + return; + } + + modem_cleanup (self); } static gboolean -component_added (NMDevice *device, GObject *component) +modem_try_claim (NMDeviceBt *self, + NMModem *modem) { - NMDeviceBt *self = NM_DEVICE_BT (device); NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE (self); - NMModem *modem; + gs_free char *rfcomm_base_name = NULL; NMDeviceState state; - NMDeviceStateReason failure_reason = NM_DEVICE_STATE_REASON_NONE; - if ( !component - || !NM_IS_MODEM (component)) + if (priv->modem) { + if (priv->modem == modem) + return TRUE; return FALSE; - - modem = NM_MODEM (component); + } if (nm_modem_is_claimed (modem)) return FALSE; - if (!priv->rfcomm_iface) + if (!priv->connect_rfcomm_iface) return FALSE; - { - gs_free char *base = NULL; - - base = g_path_get_basename (priv->rfcomm_iface); - if (!nm_streq (base, nm_modem_get_control_port (modem))) - return FALSE; - } - - /* Got the modem */ - nm_clear_g_source (&priv->timeout_id); - nm_clear_g_cancellable (&priv->cancellable); + rfcomm_base_name = g_path_get_basename (priv->connect_rfcomm_iface); + if (!nm_streq0 (rfcomm_base_name, nm_modem_get_control_port (modem))) + return FALSE; - /* Can only accept the modem in stage2, but since the interface matched + /* Can only accept the modem in stage1, but since the interface matched * what we were expecting, don't let anything else claim the modem either. */ state = nm_device_get_state (NM_DEVICE (self)); - if (state != NM_DEVICE_STATE_CONFIG) { - _LOGW (LOGD_BT | LOGD_MB, + if (state != NM_DEVICE_STATE_PREPARE) { + _LOGD (LOGD_BT | LOGD_MB, "modem found but device not in correct state (%d)", nm_device_get_state (NM_DEVICE (self))); - return TRUE; + return FALSE; } - _LOGI (LOGD_BT | LOGD_MB, - "Activation: (bluetooth) Stage 2 of 5 (Device Configure) modem found."); - - modem_cleanup (self); - priv->modem = nm_modem_claim (modem); + priv->stage1_modem_prepare_state = NM_DEVICE_STAGE_STATE_INIT; + g_signal_connect (modem, NM_MODEM_PPP_STATS, G_CALLBACK (ppp_stats), self); g_signal_connect (modem, NM_MODEM_PPP_FAILED, G_CALLBACK (ppp_failed), self); g_signal_connect (modem, NM_MODEM_PREPARE_RESULT, G_CALLBACK (modem_prepare_result), self); @@ -674,92 +695,187 @@ component_added (NMDevice *device, GObject *component) g_signal_connect (modem, NM_MODEM_AUTH_RESULT, G_CALLBACK (modem_auth_result), self); g_signal_connect (modem, NM_MODEM_STATE_CHANGED, G_CALLBACK (modem_state_cb), self); g_signal_connect (modem, NM_MODEM_REMOVED, G_CALLBACK (modem_removed_cb), self); - g_signal_connect (modem, "notify::" NM_MODEM_IP_IFINDEX, G_CALLBACK (ip_ifindex_changed_cb), self); - /* Kick off the modem connection */ - if (!modem_stage1 (self, modem, &failure_reason)) - nm_device_state_changed (NM_DEVICE (self), NM_DEVICE_STATE_FAILED, failure_reason); + _LOGD (LOGD_BT | LOGD_MB, + "modem found"); return TRUE; } -static gboolean -modem_find_timeout (gpointer user_data) +static void +mm_modem_added_cb (NMModemManager *manager, + NMModem *modem, + gpointer user_data) +{ + NMDeviceBt *self = user_data; + NMDeviceBtPrivate *priv; + + if (!modem_try_claim (user_data, modem)) + return; + + priv = NM_DEVICE_BT_GET_PRIVATE (self); + + if (priv->stage1_bt_state == NM_DEVICE_STAGE_STATE_COMPLETED) + nm_device_activate_schedule_stage1_device_prepare (NM_DEVICE (self)); +} + +/*****************************************************************************/ + +void +_nm_device_bt_notify_set_connected (NMDeviceBt *self, + gboolean connected) { - NMDeviceBt *self = NM_DEVICE_BT (user_data); NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE (self); - priv->timeout_id = 0; - nm_clear_g_cancellable (&priv->cancellable); + connected = !!connected; + if (priv->is_connected == connected) + return; + + priv->is_connected = connected; + + if ( connected + || priv->stage1_bt_state != NM_DEVICE_STAGE_STATE_COMPLETED + || nm_device_get_state (NM_DEVICE (self)) > NM_DEVICE_STATE_ACTIVATED) { + _LOGT (LOGD_BT, "set-connected: %d", connected); + return; + } + _LOGT (LOGD_BT, "set-connected: %d (disconnecting device...)", connected); nm_device_state_changed (NM_DEVICE (self), NM_DEVICE_STATE_FAILED, - NM_DEVICE_STATE_REASON_MODEM_NOT_FOUND); - return FALSE; + NM_DEVICE_STATE_REASON_CARRIER); } -static void -check_connect_continue (NMDeviceBt *self) +static gboolean +connect_watch_link_idle_cb (gpointer user_data) { - NMDevice *device = NM_DEVICE (self); + NMDeviceBt *self = user_data; NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE (self); - gboolean pan = (priv->bt_type == NM_BT_CAPABILITY_NAP); - gboolean dun = (priv->bt_type == NM_BT_CAPABILITY_DUN); + int ifindex; - if (!priv->connected || !priv->have_iface) - return; + priv->connect_watch_link_idle_id = 0; - _LOGI (LOGD_BT, - "Activation: (bluetooth) Stage 2 of 5 (Device Configure) successful. Will connect via %s.", - dun ? "DUN" : (pan ? "PAN" : "unknown")); + if (nm_device_get_state (NM_DEVICE (self)) <= NM_DEVICE_STATE_ACTIVATED) { + ifindex = nm_device_get_ip_ifindex (NM_DEVICE (self)); + if ( ifindex > 0 + && !nm_platform_link_get (nm_device_get_platform (NM_DEVICE (self)), ifindex)) { + _LOGT (LOGD_BT, "device disappeared"); + nm_device_state_changed (NM_DEVICE (self), + NM_DEVICE_STATE_FAILED, + NM_DEVICE_STATE_REASON_BT_FAILED); + } + } + + return G_SOURCE_REMOVE; +} + +static void +connect_watch_link_cb (NMPlatform *platform, + int obj_type_i, + int ifindex, + NMPlatformLink *info, + int change_type_i, + NMDevice *self) +{ + const NMPlatformSignalChangeType change_type = change_type_i; + NMDeviceBtPrivate *priv; + + /* bluez doesn't notify us when the connection disconnects. + * Neither does NMManager (or NMDevice) tell us when the ip-ifindex goes away. + * This is horrible, and should be improved. For now, watch the link ourself... */ + + if (NM_IN_SET (change_type, NM_PLATFORM_SIGNAL_CHANGED, + NM_PLATFORM_SIGNAL_REMOVED)) { + priv = NM_DEVICE_BT_GET_PRIVATE (self); + if (priv->connect_watch_link_idle_id == 0) + priv->connect_watch_link_idle_id = g_idle_add (connect_watch_link_idle_cb, self); + } +} + +static gboolean +connect_wait_modem_timeout (gpointer user_data) +{ + NMDeviceBt *self = NM_DEVICE_BT (user_data); + NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE (self); - nm_clear_g_source (&priv->timeout_id); - nm_clear_g_cancellable (&priv->cancellable); + /* since this timeout is longer than the connect timeout, we must have already + * hit the connect-timeout first or being connected. */ + nm_assert (priv->stage1_bt_state == NM_DEVICE_STAGE_STATE_COMPLETED); - if (pan) { - /* Bluez says we're connected now. Start IP config. */ - nm_device_activate_schedule_stage3_ip_config_start (device); - } else if (dun) { - /* Wait for ModemManager to find the modem */ - priv->timeout_id = g_timeout_add_seconds (30, modem_find_timeout, self); + priv->connect_wait_modem_id = 0; + nm_clear_g_cancellable (&priv->connect_bz_cancellable); - _LOGI (LOGD_BT | LOGD_MB, - "Activation: (bluetooth) Stage 2 of 5 (Device Configure) waiting for modem to appear."); - } else - g_assert_not_reached (); + if (priv->modem) + _LOGD (LOGD_BT, "timeout connecting modem for DUN connection"); + else + _LOGD (LOGD_BT, "timeout finding modem for DUN connection"); + + nm_device_state_changed (NM_DEVICE (self), + NM_DEVICE_STATE_FAILED, + NM_DEVICE_STATE_REASON_MODEM_NOT_FOUND); + return G_SOURCE_REMOVE; } static void -bluez_connect_cb (NMBluezDevice *bt_device, - const char *device_name, - GError *error, - gpointer user_data) +connect_bz_cb (NMBluezManager *bz_mgr, + gboolean is_complete, + const char *device_name, + GError *error, + gpointer user_data) { - gs_unref_object NMDeviceBt *self = user_data; - NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE (self); + NMDeviceBt *self; + NMDeviceBtPrivate *priv; + char sbuf[100]; if (nm_utils_error_is_cancelled (error, FALSE)) return; - nm_clear_g_source (&priv->timeout_id); - g_clear_object (&priv->cancellable); + self = user_data; + priv = NM_DEVICE_BT_GET_PRIVATE (self); + + nm_assert (nm_device_is_activating (NM_DEVICE (self))); + nm_assert (NM_IN_SET ((NMBluetoothCapabilities) priv->connect_bt_type, NM_BT_CAPABILITY_DUN, + NM_BT_CAPABILITY_NAP)); + + if (!is_complete) { + nm_assert (priv->connect_bt_type == NM_BT_CAPABILITY_DUN); + nm_assert (device_name); + nm_assert (!error); - if (!nm_device_is_activating (NM_DEVICE (self))) + if (!nm_streq0 (priv->connect_rfcomm_iface, device_name)) { + nm_assert (!priv->connect_rfcomm_iface); + _LOGD (LOGD_BT, "DUN is still connecting but got serial port \"%s\" to claim modem", device_name); + g_free (priv->connect_rfcomm_iface); + priv->connect_rfcomm_iface = g_strdup (device_name); + } return; + } + + g_clear_object (&priv->connect_bz_cancellable); if (!device_name) { - _LOGW (LOGD_BT, "Error connecting with bluez: %s", error->message); + _LOGW (LOGD_BT, "%s connect request failed: %s", + nm_bluetooth_capability_to_string (priv->connect_bt_type, sbuf, sizeof (sbuf)), + error->message); nm_device_state_changed (NM_DEVICE (self), NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_BT_FAILED); return; } - if (priv->bt_type == NM_BT_CAPABILITY_DUN) { - g_free (priv->rfcomm_iface); - priv->rfcomm_iface = g_strdup (device_name); - } else if (priv->bt_type == NM_BT_CAPABILITY_NAP) { + _LOGD (LOGD_BT, "%s connect request successful (%s)", + nm_bluetooth_capability_to_string (priv->connect_bt_type, sbuf, sizeof (sbuf)), + device_name); + + if (priv->connect_bt_type == NM_BT_CAPABILITY_DUN) { + if (!nm_streq0 (priv->connect_rfcomm_iface, device_name)) { + nm_assert_not_reached (); + g_free (priv->connect_rfcomm_iface); + priv->connect_rfcomm_iface = g_strdup (device_name); + } + } else { + nm_assert (priv->connect_bt_type == NM_BT_CAPABILITY_NAP); if (!nm_device_set_ip_iface (NM_DEVICE (self), device_name)) { _LOGW (LOGD_BT, "Error connecting with bluez: cannot find device %s", device_name); nm_device_state_changed (NM_DEVICE (self), @@ -767,107 +883,121 @@ bluez_connect_cb (NMBluezDevice *bt_device, NM_DEVICE_STATE_REASON_BT_FAILED); return; } + priv->connect_watch_link_id = g_signal_connect (nm_device_get_platform (NM_DEVICE (self)), + NM_PLATFORM_SIGNAL_LINK_CHANGED, + G_CALLBACK (connect_watch_link_cb), + self); } - _LOGD (LOGD_BT, "connect request successful"); - - /* Stage 3 gets scheduled when Bluez says we're connected */ - priv->have_iface = TRUE; - check_connect_continue (self); -} - -static void -bluez_connected_changed (NMBluezDevice *bt_device, - GParamSpec *pspec, - NMDevice *device) -{ - NMDeviceBt *self = NM_DEVICE_BT (device); - NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE (self); - gboolean connected; - NMDeviceState state; - - state = nm_device_get_state (device); - connected = nm_bluez_device_get_connected (bt_device); - if (connected) { - if (state == NM_DEVICE_STATE_CONFIG) { - _LOGD (LOGD_BT, "connected to the device"); - - priv->connected = TRUE; - check_connect_continue (self); - } - } else { - gboolean fail = FALSE; - - /* Bluez says we're disconnected from the device. Suck. */ - - if (nm_device_is_activating (device)) { - _LOGI (LOGD_BT, "Activation: (bluetooth) bluetooth link disconnected."); - fail = TRUE; - } else if (state == NM_DEVICE_STATE_ACTIVATED) { - _LOGI (LOGD_BT, "bluetooth link disconnected."); - fail = TRUE; - } - - if (fail) { - nm_device_state_changed (device, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_CARRIER); - priv->connected = FALSE; - } + if (!priv->is_connected) { + /* we got the callback from NMBluezManager with succes. We actually should be + * connected and this line shouldn't be reached. */ + nm_assert_not_reached (); + _LOGE (LOGD_BT, "bluetooth is unexpectedly not in connected state"); + nm_device_state_changed (NM_DEVICE (self), + NM_DEVICE_STATE_FAILED, + NM_DEVICE_STATE_REASON_BT_FAILED); + return; } -} -static gboolean -bt_connect_timeout (gpointer user_data) -{ - NMDeviceBt *self = NM_DEVICE_BT (user_data); - NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE (self); - - _LOGD (LOGD_BT, "initial connection timed out"); - - priv->timeout_id = 0; - nm_clear_g_cancellable (&priv->cancellable); - - nm_device_state_changed (NM_DEVICE (self), - NM_DEVICE_STATE_FAILED, - NM_DEVICE_STATE_REASON_BT_FAILED); - return FALSE; + priv->stage1_bt_state = NM_DEVICE_STAGE_STATE_COMPLETED; + nm_device_activate_schedule_stage1_device_prepare (NM_DEVICE (self)); } static NMActStageReturn -act_stage2_config (NMDevice *device, NMDeviceStateReason *out_failure_reason) +act_stage1_prepare (NMDevice *device, + NMDeviceStateReason *out_failure_reason) { NMDeviceBt *self = NM_DEVICE_BT (device); NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE (self); + gs_free_error GError *error = NULL; NMConnection *connection; connection = nm_device_get_applied_connection (device); g_return_val_if_fail (connection, NM_ACT_STAGE_RETURN_FAILURE); - priv->bt_type = get_connection_bt_type (connection); - if (priv->bt_type == NM_BT_CAPABILITY_NONE) { - // FIXME: set a reason code + priv->connect_bt_type = get_connection_bt_type (connection); + if (priv->connect_bt_type == NM_BT_CAPABILITY_NONE) { + NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_BT_FAILED); return NM_ACT_STAGE_RETURN_FAILURE; } - if (priv->bt_type == NM_BT_CAPABILITY_DUN && !priv->mm_running) { + if ( priv->connect_bt_type == NM_BT_CAPABILITY_DUN + && !priv->mm_running) { NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_MODEM_MANAGER_UNAVAILABLE); return NM_ACT_STAGE_RETURN_FAILURE; } - _LOGD (LOGD_BT, "requesting connection to the device"); + if (priv->stage1_bt_state == NM_DEVICE_STAGE_STATE_PENDING) + return NM_ACT_STAGE_RETURN_POSTPONE; + else if (priv->stage1_bt_state == NM_DEVICE_STAGE_STATE_INIT) { + gs_unref_object GCancellable *cancellable = NULL; + char sbuf[100]; + + _LOGD (LOGD_BT, "connecting to %s bluetooth device", + nm_bluetooth_capability_to_string (priv->connect_bt_type, sbuf, sizeof (sbuf))); + + cancellable = g_cancellable_new (); + + if (!nm_bluez_manager_connect (priv->bz_mgr, + priv->dbus_path, + priv->connect_bt_type, + 30000, + cancellable, + connect_bz_cb, + self, + &error)) { + _LOGD (LOGD_BT, "cannot connect to bluetooth device: %s", error->message); + *out_failure_reason = NM_DEVICE_STATE_REASON_BT_FAILED; + return NM_ACT_STAGE_RETURN_FAILURE; + } + + priv->connect_bz_cancellable = g_steal_pointer (&cancellable); + priv->stage1_bt_state = NM_DEVICE_STAGE_STATE_PENDING; + return NM_ACT_STAGE_RETURN_POSTPONE; + } + + if (priv->connect_bt_type == NM_BT_CAPABILITY_DUN) { + if (!priv->modem) { + gs_free NMModem **modems = NULL; + guint i, n; + + if (priv->connect_wait_modem_id == 0) + priv->connect_wait_modem_id = g_timeout_add_seconds (30, connect_wait_modem_timeout, self); + + modems = nm_modem_manager_get_modems (priv->modem_manager, &n); + for (i = 0; i < n; i++) { + if (modem_try_claim (self, modems[i])) + break; + } + if (!priv->modem) + return NM_ACT_STAGE_RETURN_POSTPONE; + } + + if (priv->stage1_modem_prepare_state == NM_DEVICE_STAGE_STATE_PENDING) + return NM_ACT_STAGE_RETURN_POSTPONE; + if (priv->stage1_modem_prepare_state == NM_DEVICE_STAGE_STATE_INIT) { + priv->stage1_modem_prepare_state = NM_DEVICE_STAGE_STATE_PENDING; + return nm_modem_act_stage1_prepare (priv->modem, + nm_device_get_act_request (NM_DEVICE (self)), + out_failure_reason); + } + } - nm_clear_g_source (&priv->timeout_id); - nm_clear_g_cancellable (&priv->cancellable); + return NM_ACT_STAGE_RETURN_SUCCESS; +} - priv->timeout_id = g_timeout_add_seconds (30, bt_connect_timeout, device); - priv->cancellable = g_cancellable_new (); +static NMActStageReturn +act_stage2_config (NMDevice *device, + NMDeviceStateReason *out_failure_reason) +{ + NMDeviceBt *self = NM_DEVICE_BT (device); + NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE (self); - nm_bluez_device_connect_async (priv->bt_device, - priv->bt_type & (NM_BT_CAPABILITY_DUN | NM_BT_CAPABILITY_NAP), - priv->cancellable, - bluez_connect_cb, - g_object_ref (self)); + if (priv->connect_bt_type == NM_BT_CAPABILITY_DUN) + nm_modem_act_stage2_config (priv->modem); - return NM_ACT_STAGE_RETURN_POSTPONE; + return NM_ACT_STAGE_RETURN_SUCCESS; } static NMActStageReturn @@ -876,11 +1006,11 @@ act_stage3_ip_config_start (NMDevice *device, gpointer *out_config, NMDeviceStateReason *out_failure_reason) { - NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE ((NMDeviceBt *) device); + NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE (device); nm_assert_addr_family (addr_family); - if (priv->bt_type == NM_BT_CAPABILITY_DUN) { + if (priv->connect_bt_type == NM_BT_CAPABILITY_DUN) { if (addr_family == AF_INET) { return nm_modem_stage3_ip4_config_start (priv->modem, device, @@ -899,15 +1029,17 @@ act_stage3_ip_config_start (NMDevice *device, static void deactivate (NMDevice *device) { - NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE ((NMDeviceBt *) device); + NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE (device); - priv->have_iface = FALSE; - priv->connected = FALSE; + nm_clear_g_signal_handler (nm_device_get_platform (device), &priv->connect_watch_link_id); + nm_clear_g_source (&priv->connect_watch_link_idle_id); + priv->stage1_bt_state = NM_DEVICE_STAGE_STATE_INIT; + nm_clear_g_source (&priv->connect_wait_modem_id); + nm_clear_g_cancellable (&priv->connect_bz_cancellable); - nm_clear_g_source (&priv->timeout_id); - nm_clear_g_cancellable (&priv->cancellable); + priv->stage1_bt_state = NM_DEVICE_STAGE_STATE_INIT; - if (priv->bt_type == NM_BT_CAPABILITY_DUN) { + if (priv->connect_bt_type == NM_BT_CAPABILITY_DUN) { if (priv->modem) { nm_modem_deactivate (priv->modem, device); @@ -921,22 +1053,53 @@ deactivate (NMDevice *device) } } - if (priv->bt_type != NM_BT_CAPABILITY_NONE) - nm_bluez_device_disconnect (priv->bt_device); - - priv->bt_type = NM_BT_CAPABILITY_NONE; + if (priv->connect_bt_type != NM_BT_CAPABILITY_NONE) { + priv->connect_bt_type = NM_BT_CAPABILITY_NONE; + nm_bluez_manager_disconnect (priv->bz_mgr, priv->dbus_path); + } - g_free (priv->rfcomm_iface); - priv->rfcomm_iface = NULL; + nm_clear_g_free (&priv->connect_rfcomm_iface); if (NM_DEVICE_CLASS (nm_device_bt_parent_class)->deactivate) NM_DEVICE_CLASS (nm_device_bt_parent_class)->deactivate (device); } -static void -bluez_device_removed (NMBluezDevice *bdev, gpointer user_data) +void +_nm_device_bt_notify_removed (NMDeviceBt *self) { - g_signal_emit_by_name (NM_DEVICE_BT (user_data), NM_DEVICE_REMOVED); + g_signal_emit_by_name (self, NM_DEVICE_REMOVED); +} + +/*****************************************************************************/ + +gboolean +_nm_device_bt_for_same_device (NMDeviceBt *self, + const char *dbus_path, + const char *bdaddr, + const char *name, + NMBluetoothCapabilities capabilities) +{ + NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE (self); + + return nm_streq (priv->dbus_path, dbus_path) + && nm_streq (priv->bdaddr, bdaddr) + && capabilities == priv->capabilities + && (!name || nm_streq (priv->name, name)); +} + +void +_nm_device_bt_notify_set_name (NMDeviceBt *self, const char *name) +{ + NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE (self); + + nm_assert (name); + + if (!nm_streq (priv->name, name)) { + _LOGT (LOGD_BT, "set-name: %s", name); + g_free (priv->name); + priv->name = g_strdup (name); + _notify (self, PROP_BT_NAME); + } } /*****************************************************************************/ @@ -997,9 +1160,6 @@ get_property (GObject *object, guint prop_id, case PROP_BT_CAPABILITIES: g_value_set_uint (value, priv->capabilities); break; - case PROP_BT_DEVICE: - g_value_set_object (value, priv->bt_device); - break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -1013,19 +1173,32 @@ set_property (GObject *object, guint prop_id, NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE ((NMDeviceBt *) object); switch (prop_id) { + case PROP_BT_BZ_MGR: + /* construct-only */ + priv->bz_mgr = g_object_ref (g_value_get_pointer (value)); + nm_assert (NM_IS_BLUEZ_MANAGER (priv->bz_mgr)); + break; + case PROP_BT_DBUS_PATH: + /* construct-only */ + priv->dbus_path = g_value_dup_string (value); + nm_assert (priv->dbus_path); + break; + case PROP_BT_BDADDR: + /* construct-only */ + priv->bdaddr = g_value_dup_string (value); + nm_assert (priv->bdaddr); + break; case PROP_BT_NAME: /* construct-only */ priv->name = g_value_dup_string (value); + nm_assert (priv->name); break; case PROP_BT_CAPABILITIES: /* construct-only */ priv->capabilities = g_value_get_uint (value); - break; - case PROP_BT_DEVICE: - /* construct-only */ - priv->bt_device = g_value_dup_object (value); - if (!priv->bt_device) - g_return_if_reached (); + nm_assert (NM_IN_SET ((NMBluetoothCapabilities) priv->capabilities, NM_BT_CAPABILITY_DUN, + NM_BT_CAPABILITY_NAP, + NM_BT_CAPABILITY_DUN | NM_BT_CAPABILITY_NAP)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); @@ -1045,7 +1218,6 @@ constructed (GObject *object) { NMDeviceBt *self = NM_DEVICE_BT (object); NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE (self); - const char *my_hwaddr; G_OBJECT_CLASS (nm_device_bt_parent_class)->constructed (object); @@ -1054,74 +1226,69 @@ constructed (GObject *object) nm_modem_manager_name_owner_ref (priv->modem_manager); g_signal_connect (priv->modem_manager, + NM_MODEM_MANAGER_MODEM_ADDED, + G_CALLBACK (mm_modem_added_cb), + self); + + g_signal_connect (priv->modem_manager, "notify::"NM_MODEM_MANAGER_NAME_OWNER, G_CALLBACK (mm_name_owner_changed_cb), self); - if (priv->bt_device) { - /* Watch for BT device property changes */ - g_signal_connect (priv->bt_device, "notify::" NM_BLUEZ_DEVICE_CONNECTED, - G_CALLBACK (bluez_connected_changed), - object); - g_signal_connect (priv->bt_device, NM_BLUEZ_DEVICE_REMOVED, - G_CALLBACK (bluez_device_removed), object); - } - - my_hwaddr = nm_device_get_hw_address (NM_DEVICE (object)); - if (my_hwaddr) - priv->bdaddr = g_strdup (my_hwaddr); - else - g_warn_if_reached (); - set_mm_running (self); } -NMDevice * -nm_device_bt_new (NMBluezDevice *bt_device, - const char *udi, +NMDeviceBt * +nm_device_bt_new (NMBluezManager *bz_mgr, + const char *dbus_path, const char *bdaddr, const char *name, - guint32 capabilities) + NMBluetoothCapabilities capabilities) { - g_return_val_if_fail (udi != NULL, NULL); - g_return_val_if_fail (bdaddr != NULL, NULL); - g_return_val_if_fail (name != NULL, NULL); + g_return_val_if_fail (NM_IS_BLUEZ_MANAGER (bz_mgr), NULL); + g_return_val_if_fail (dbus_path, NULL); + g_return_val_if_fail (bdaddr, NULL); + g_return_val_if_fail (name, NULL); g_return_val_if_fail (capabilities != NM_BT_CAPABILITY_NONE, NULL); - g_return_val_if_fail (NM_IS_BLUEZ_DEVICE (bt_device), NULL); - - return (NMDevice *) g_object_new (NM_TYPE_DEVICE_BT, - NM_DEVICE_UDI, udi, - NM_DEVICE_IFACE, bdaddr, - NM_DEVICE_DRIVER, "bluez", - NM_DEVICE_PERM_HW_ADDRESS, bdaddr, - NM_DEVICE_BT_DEVICE, bt_device, - NM_DEVICE_BT_NAME, name, - NM_DEVICE_BT_CAPABILITIES, capabilities, - NM_DEVICE_TYPE_DESC, "Bluetooth", - NM_DEVICE_DEVICE_TYPE, NM_DEVICE_TYPE_BT, - NULL); + + return g_object_new (NM_TYPE_DEVICE_BT, + NM_DEVICE_UDI, dbus_path, + NM_DEVICE_IFACE, bdaddr, + NM_DEVICE_DRIVER, "bluez", + NM_DEVICE_PERM_HW_ADDRESS, bdaddr, + NM_DEVICE_BT_BDADDR, bdaddr, + NM_DEVICE_BT_BZ_MGR, bz_mgr, + NM_DEVICE_BT_CAPABILITIES, (guint) capabilities, + NM_DEVICE_BT_DBUS_PATH, dbus_path, + NM_DEVICE_BT_NAME, name, + NM_DEVICE_TYPE_DESC, "Bluetooth", + NM_DEVICE_DEVICE_TYPE, NM_DEVICE_TYPE_BT, + NULL); } static void dispose (GObject *object) { - NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE ((NMDeviceBt *) object); + NMDeviceBt *self = NM_DEVICE_BT (object); + NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE (self); - nm_clear_g_source (&priv->timeout_id); - nm_clear_g_cancellable (&priv->cancellable); + nm_clear_g_signal_handler (nm_device_get_platform (NM_DEVICE (self)), &priv->connect_watch_link_id); + nm_clear_g_source (&priv->connect_watch_link_idle_id); - g_signal_handlers_disconnect_matched (priv->bt_device, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, object); + nm_clear_g_source (&priv->connect_wait_modem_id); + nm_clear_g_cancellable (&priv->connect_bz_cancellable); if (priv->modem_manager) { - g_signal_handlers_disconnect_by_func (priv->modem_manager, G_CALLBACK (mm_name_owner_changed_cb), object); + g_signal_handlers_disconnect_by_func (priv->modem_manager, G_CALLBACK (mm_name_owner_changed_cb), self); nm_modem_manager_name_owner_unref (priv->modem_manager); g_clear_object (&priv->modem_manager); } - modem_cleanup (NM_DEVICE_BT (object)); - g_clear_object (&priv->bt_device); + modem_cleanup (self); G_OBJECT_CLASS (nm_device_bt_parent_class)->dispose (object); + + g_clear_object (&priv->bz_mgr); } static void @@ -1129,7 +1296,8 @@ finalize (GObject *object) { NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE ((NMDeviceBt *) object); - g_free (priv->rfcomm_iface); + g_free (priv->connect_rfcomm_iface); + g_free (priv->dbus_path); g_free (priv->name); g_free (priv->bdaddr); @@ -1171,17 +1339,34 @@ nm_device_bt_class_init (NMDeviceBtClass *klass) device_class->get_generic_capabilities = get_generic_capabilities; device_class->can_auto_connect = can_auto_connect; device_class->deactivate = deactivate; + device_class->act_stage1_prepare = act_stage1_prepare; device_class->act_stage2_config = act_stage2_config; device_class->act_stage3_ip_config_start = act_stage3_ip_config_start; device_class->check_connection_compatible = check_connection_compatible; device_class->check_connection_available = check_connection_available; device_class->complete_connection = complete_connection; device_class->is_available = is_available; - device_class->component_added = component_added; device_class->get_configured_mtu = nm_modem_get_configured_mtu; device_class->state_changed = device_state_changed; + obj_properties[PROP_BT_BZ_MGR] = + g_param_spec_pointer (NM_DEVICE_BT_BZ_MGR, "", "", + G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_BT_BDADDR] = + g_param_spec_string (NM_DEVICE_BT_BDADDR, "", "", + NULL, + G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_BT_DBUS_PATH] = + g_param_spec_string (NM_DEVICE_BT_DBUS_PATH, "", "", + NULL, + G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS); + obj_properties[PROP_BT_NAME] = g_param_spec_string (NM_DEVICE_BT_NAME, "", "", NULL, @@ -1194,12 +1379,6 @@ nm_device_bt_class_init (NMDeviceBtClass *klass) G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); - obj_properties[PROP_BT_DEVICE] = - g_param_spec_object (NM_DEVICE_BT_DEVICE, "", "", - NM_TYPE_BLUEZ_DEVICE, - G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | - G_PARAM_STATIC_STRINGS); - g_object_class_install_properties (object_class, _PROPERTY_ENUMS_LAST, obj_properties); signals[PPP_STATS] = diff --git a/src/devices/bluetooth/nm-device-bt.h b/src/devices/bluetooth/nm-device-bt.h index 2a0eb1de2c..d889aefefb 100644 --- a/src/devices/bluetooth/nm-device-bt.h +++ b/src/devices/bluetooth/nm-device-bt.h @@ -8,7 +8,6 @@ #define __NETWORKMANAGER_DEVICE_BT_H__ #include "devices/nm-device.h" -#include "nm-bluez-device.h" #define NM_TYPE_DEVICE_BT (nm_device_bt_get_type ()) #define NM_DEVICE_BT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_DEVICE_BT, NMDeviceBt)) @@ -17,9 +16,11 @@ #define NM_IS_DEVICE_BT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NM_TYPE_DEVICE_BT)) #define NM_DEVICE_BT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), NM_TYPE_DEVICE_BT, NMDeviceBtClass)) -#define NM_DEVICE_BT_NAME "name" +#define NM_DEVICE_BT_BDADDR "bt-bdaddr" +#define NM_DEVICE_BT_BZ_MGR "bt-bz-mgr" #define NM_DEVICE_BT_CAPABILITIES "bt-capabilities" -#define NM_DEVICE_BT_DEVICE "bt-device" +#define NM_DEVICE_BT_DBUS_PATH "bt-dbus-path" +#define NM_DEVICE_BT_NAME "bt-name" #define NM_DEVICE_BT_PPP_STATS "ppp-stats" @@ -28,13 +29,21 @@ typedef struct _NMDeviceBtClass NMDeviceBtClass; GType nm_device_bt_get_type (void); -NMDevice *nm_device_bt_new (NMBluezDevice *bt_device, - const char *udi, - const char *bdaddr, - const char *name, - guint32 capabilities); +struct _NMBluezManager; -guint32 nm_device_bt_get_capabilities (NMDeviceBt *device); +NMDeviceBt *nm_device_bt_new (struct _NMBluezManager *bz_mgr, + const char *dbus_path, + const char *bdaddr, + const char *name, + NMBluetoothCapabilities capabilities); + +gboolean _nm_device_bt_for_same_device (NMDeviceBt *device, + const char *dbus_path, + const char *bdaddr, + const char *name, + NMBluetoothCapabilities capabilities); + +NMBluetoothCapabilities nm_device_bt_get_capabilities (NMDeviceBt *device); struct _NMModem; @@ -42,4 +51,11 @@ gboolean nm_device_bt_modem_added (NMDeviceBt *device, struct _NMModem *modem, const char *driver); +void _nm_device_bt_notify_removed (NMDeviceBt *self); + +void _nm_device_bt_notify_set_name (NMDeviceBt *self, const char *name); + +void _nm_device_bt_notify_set_connected (NMDeviceBt *self, + gboolean connected); + #endif /* __NETWORKMANAGER_DEVICE_BT_H__ */ diff --git a/src/devices/bluetooth/tests/nm-bt-test.c b/src/devices/bluetooth/tests/nm-bt-test.c index 42f3f2e553..02cfd2288f 100644 --- a/src/devices/bluetooth/tests/nm-bt-test.c +++ b/src/devices/bluetooth/tests/nm-bt-test.c @@ -2,35 +2,219 @@ #include "nm-default.h" +#include <glib-unix.h> + #include "devices/bluetooth/nm-bluez5-dun.h" #include "nm-test-utils-core.h" /*****************************************************************************/ +#define _NMLOG_DOMAIN LOGD_BT +#define _NMLOG(level, ...) \ + nm_log ((level), _NMLOG_DOMAIN, \ + NULL, NULL, \ + "bt%s%s%s: " _NM_UTILS_MACRO_FIRST (__VA_ARGS__), \ + NM_PRINT_FMT_QUOTED (gl.argv_cmd, "[", gl.argv_cmd, "]", "") \ + _NM_UTILS_MACRO_REST (__VA_ARGS__)) + +/*****************************************************************************/ + +struct { + int argc; + const char *const*argv; + const char *argv_cmd; + GMainLoop *loop; +} gl; + +typedef struct _MainCmdInfo { + const char *name; + int (*main_func) (const struct _MainCmdInfo *main_cmd_info); +} MainCmdInfo; + +/*****************************************************************************/ + +#if WITH_BLUEZ5_DUN + +typedef struct { + NMBluez5DunContext *dun_context; + GCancellable *cancellable; + guint timeout_id; + guint sig_term_id; + guint sig_int_id; +} DunConnectData; + +static void +_dun_connect_cb (NMBluez5DunContext *context, + const char *rfcomm_dev, + GError *error, + gpointer user_data) +{ + DunConnectData *dun_connect_data = user_data; + + g_assert (dun_connect_data); + g_assert (!dun_connect_data->dun_context); + g_assert ((!!error) != (!!rfcomm_dev)); + + if (rfcomm_dev && !context) { + _LOGI ("dun-connect notifies path \"%s\". Wait longer...", rfcomm_dev); + return; + } + + if (rfcomm_dev) { + g_assert (context); + _LOGI ("dun-connect completed with path \"%s\"", rfcomm_dev); + } else { + g_assert (!context); + _LOGI ("dun-connect failed with error: %s", error->message); + } + + dun_connect_data->dun_context = context; + + g_main_loop_quit (gl.loop); +} + +static void +_dun_notify_tty_hangup_cb (NMBluez5DunContext *context, + gpointer user_data) +{ + _LOGI ("dun-connect: notified TTY hangup"); +} + +static gboolean +_timeout_cb (gpointer user_data) +{ + DunConnectData *dun_connect_data = user_data; + + _LOGI ("timeout"); + dun_connect_data->timeout_id = 0; + if (dun_connect_data->cancellable) + g_cancellable_cancel (dun_connect_data->cancellable); + return G_SOURCE_REMOVE; +} + +static gboolean +_sig_xxx_cb (DunConnectData *dun_connect_data, int sigid) +{ + _LOGI ("signal %s received", sigid == SIGTERM ? "SIGTERM" : "SIGINT"); + g_main_loop_quit (gl.loop); + return G_SOURCE_CONTINUE; +} + +static gboolean +_sig_term_cb (gpointer user_data) +{ + return _sig_xxx_cb (user_data, SIGTERM); +} + +static gboolean +_sig_int_cb (gpointer user_data) +{ + return _sig_xxx_cb (user_data, SIGINT); +} +#endif + +static int +do_dun_connect (const MainCmdInfo *main_cmd_info) +{ +#if WITH_BLUEZ5_DUN + gs_unref_object GCancellable *cancellable = NULL; + gs_free_error GError *error = NULL; + const char *adapter; + const char *remote; + DunConnectData dun_connect_data = { }; + + if (gl.argc < 4) { + _LOGE ("missing arguments \"adapter\" and \"remote\""); + return -1; + } + + adapter = gl.argv[2]; + remote = gl.argv[3]; + + cancellable = g_cancellable_new (); + dun_connect_data.cancellable = cancellable; + + if (!nm_bluez5_dun_connect (adapter, + remote, + cancellable, + _dun_connect_cb, + &dun_connect_data, + _dun_notify_tty_hangup_cb, + &dun_connect_data, + &error)) { + _LOGE ("connect failed to start: %s", error->message); + return -1; + } + + dun_connect_data.timeout_id = g_timeout_add (60000, _timeout_cb, &dun_connect_data); + + g_main_loop_run (gl.loop); + + nm_clear_g_source (&dun_connect_data.timeout_id); + + if (dun_connect_data.dun_context) { + + dun_connect_data.sig_term_id = g_unix_signal_add (SIGTERM, _sig_term_cb, &dun_connect_data); + dun_connect_data.sig_int_id = g_unix_signal_add (SIGINT, _sig_int_cb, &dun_connect_data); + + g_main_loop_run (gl.loop); + + nm_clear_g_source (&dun_connect_data.sig_term_id); + nm_clear_g_source (&dun_connect_data.sig_int_id); + + nm_bluez5_dun_disconnect (g_steal_pointer (&dun_connect_data.dun_context)); + } + + return 0; +#else + _LOGE ("compiled without bluetooth DUN support"); + return 1; +#endif +} + +/*****************************************************************************/ + NMTST_DEFINE (); int main (int argc, char **argv) { - NMBluez5DunContext *dun_context; - GMainLoop *loop; + static const MainCmdInfo main_cmd_infos[] = { + { .name = "dun-connect", .main_func = do_dun_connect, }, + }; + int exit_code = 0; + guint i; if (!g_getenv ("G_MESSAGES_DEBUG")) g_setenv ("G_MESSAGES_DEBUG", "all", TRUE); nmtst_init_with_logging (&argc, &argv, "DEBUG", "ALL"); - nm_log_info (LOGD_BT, "bluetooth test util start"); + nm_logging_init (NULL, TRUE); + + gl.argv = (const char *const*) argv; + gl.argc = argc; + gl.loop = g_main_loop_new (NULL, FALSE); - dun_context = nm_bluez5_dun_new ("aa:bb:cc:dd:ee:ff", - "aa:bb:cc:dd:ee:fa"); + _LOGI ("bluetooth test util start"); - loop = g_main_loop_new (NULL, FALSE); + gl.argv_cmd = argc >= 2 ? argv[1] : NULL; - g_main_loop_unref (loop); + for (i = 0; i < G_N_ELEMENTS (main_cmd_infos); i++) { + if (nm_streq0 (main_cmd_infos[i].name, gl.argv_cmd)) { + _LOGD ("start \"%s\"", gl.argv_cmd); + exit_code = main_cmd_infos[i].main_func (&main_cmd_infos[i]); + _LOGD ("completed with %d", exit_code); + break; + } + } + if (gl.argv_cmd && i >= G_N_ELEMENTS (main_cmd_infos)) { + nm_log_err (LOGD_BT, "invalid command \"%s\"", gl.argv_cmd); + exit_code = -1; + } - nm_bluez5_dun_free (dun_context); + nm_clear_pointer (&gl.loop, g_main_loop_unref); - return EXIT_SUCCESS; + return exit_code; } diff --git a/src/devices/nm-device-bridge.c b/src/devices/nm-device-bridge.c index 66dc6dc809..aadaa7faee 100644 --- a/src/devices/nm-device-bridge.c +++ b/src/devices/nm-device-bridge.c @@ -23,7 +23,9 @@ _LOG_DECLARE_SELF(NMDeviceBridge); struct _NMDeviceBridge { NMDevice parent; + GCancellable *bt_cancellable; bool vlan_configured:1; + bool bt_registered:1; }; struct _NMDeviceBridgeClass { @@ -51,6 +53,7 @@ check_connection_available (NMDevice *device, const char *specific_object, GError **error) { + NMDeviceBridge *self = NM_DEVICE_BRIDGE (device); NMSettingBluetooth *s_bt; if (!NM_DEVICE_CLASS (nm_device_bridge_parent_class)->check_connection_available (device, connection, flags, specific_object, error)) @@ -67,13 +70,18 @@ check_connection_available (NMDevice *device, } bdaddr = nm_setting_bluetooth_get_bdaddr (s_bt); - if (!nm_bt_vtable_network_server->is_available (nm_bt_vtable_network_server, bdaddr)) { + if (!nm_bt_vtable_network_server->is_available (nm_bt_vtable_network_server, + bdaddr, + ( self->bt_cancellable + || self->bt_registered) + ? device + : NULL)) { if (bdaddr) nm_utils_error_set (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, - "not suitable NAP device \"%s\" available", bdaddr); + "no suitable NAP device \"%s\" available", bdaddr); else nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, - "not suitable NAP device available"); + "no suitable NAP device available"); return FALSE; } } @@ -505,9 +513,53 @@ act_stage1_prepare (NMDevice *device, NMDeviceStateReason *out_failure_reason) return NM_ACT_STAGE_RETURN_SUCCESS; } +static void +_bt_register_bridge_cb (GError *error, + gpointer user_data) +{ + NMDeviceBridge *self; + + if (nm_utils_error_is_cancelled (error, FALSE)) + return; + + self = user_data; + + g_clear_object (&self->bt_cancellable); + + if (error) { + _LOGD (LOGD_DEVICE, "bluetooth NAP server failed to register bridge: %s", error->message); + nm_device_state_changed (NM_DEVICE (self), NM_DEVICE_STATE_FAILED, + NM_DEVICE_STATE_REASON_BT_FAILED); + return; + } + + nm_device_activate_schedule_stage3_ip_config_start (NM_DEVICE (self)); +} + +void +_nm_device_bridge_notify_unregister_bt_nap (NMDevice *device, + const char *reason) +{ + NMDeviceBridge *self = NM_DEVICE_BRIDGE (device); + + _LOGD (LOGD_DEVICE, "bluetooth NAP server unregistered from bridge: %s%s", + reason, + self->bt_registered ? "" : " (was no longer registered)"); + + nm_clear_g_cancellable (&self->bt_cancellable); + + if (self->bt_registered) { + self->bt_registered = FALSE; + nm_device_state_changed (device, + NM_DEVICE_STATE_FAILED, + NM_DEVICE_STATE_REASON_BT_FAILED); + } +} + static NMActStageReturn act_stage2_config (NMDevice *device, NMDeviceStateReason *out_failure_reason) { + NMDeviceBridge *self = NM_DEVICE_BRIDGE (device); NMConnection *connection; NMSettingBluetooth *s_bt; @@ -515,14 +567,32 @@ act_stage2_config (NMDevice *device, NMDeviceStateReason *out_failure_reason) s_bt = _nm_connection_get_setting_bluetooth_for_nap (connection); if (s_bt) { - if ( !nm_bt_vtable_network_server - || !nm_bt_vtable_network_server->register_bridge (nm_bt_vtable_network_server, - nm_setting_bluetooth_get_bdaddr (s_bt), - device)) { - /* The HCI we could use is no longer present. */ - *out_failure_reason = NM_DEVICE_STATE_REASON_REMOVED; + gs_free_error GError *error = NULL; + + if (!nm_bt_vtable_network_server) { + _LOGD (LOGD_DEVICE, "bluetooth NAP server failed because bluetooth plugin not available"); + *out_failure_reason = NM_DEVICE_STATE_REASON_BT_FAILED; + return NM_ACT_STAGE_RETURN_FAILURE; + } + + if (self->bt_cancellable) + return NM_ACT_STAGE_RETURN_POSTPONE; + + self->bt_cancellable = g_cancellable_new (); + if (!nm_bt_vtable_network_server->register_bridge (nm_bt_vtable_network_server, + nm_setting_bluetooth_get_bdaddr (s_bt), + device, + self->bt_cancellable, + _bt_register_bridge_cb, + device, + &error)) { + _LOGD (LOGD_DEVICE, "bluetooth NAP server failed to register bridge: %s", error->message); + *out_failure_reason = NM_DEVICE_STATE_REASON_BT_FAILED; return NM_ACT_STAGE_RETURN_FAILURE; } + + self->bt_registered = TRUE; + return NM_ACT_STAGE_RETURN_POSTPONE; } return NM_ACT_STAGE_RETURN_SUCCESS; @@ -533,11 +603,15 @@ deactivate (NMDevice *device) { NMDeviceBridge *self = NM_DEVICE_BRIDGE (device); + _LOGD (LOGD_DEVICE, "deactivate bridge%s", + self->bt_registered ? " (registered as NAP bluetooth device)" : ""); + self->vlan_configured = FALSE; - if (nm_bt_vtable_network_server) { - /* always call unregister. It does nothing if the device - * isn't registered as a hotspot bridge. */ + nm_clear_g_cancellable (&self->bt_cancellable); + + if (self->bt_registered) { + self->bt_registered = FALSE; nm_bt_vtable_network_server->unregister_bridge (nm_bt_vtable_network_server, device); } diff --git a/src/devices/nm-device-bridge.h b/src/devices/nm-device-bridge.h index 6830a84a13..1caf0f1f01 100644 --- a/src/devices/nm-device-bridge.h +++ b/src/devices/nm-device-bridge.h @@ -23,4 +23,7 @@ GType nm_device_bridge_get_type (void); extern const NMBtVTableNetworkServer *nm_bt_vtable_network_server; +void _nm_device_bridge_notify_unregister_bt_nap (NMDevice *device, + const char *reason); + #endif /* __NETWORKMANAGER_DEVICE_BRIDGE_H__ */ diff --git a/src/devices/nm-device-factory.c b/src/devices/nm-device-factory.c index bf4d490d52..6633c35fb0 100644 --- a/src/devices/nm-device-factory.c +++ b/src/devices/nm-device-factory.c @@ -23,7 +23,6 @@ enum { DEVICE_ADDED, - COMPONENT_ADDED, LAST_SIGNAL }; @@ -33,17 +32,6 @@ G_DEFINE_ABSTRACT_TYPE (NMDeviceFactory, nm_device_factory, G_TYPE_OBJECT) /*****************************************************************************/ -gboolean -nm_device_factory_emit_component_added (NMDeviceFactory *factory, GObject *component) -{ - gboolean consumed = FALSE; - - g_return_val_if_fail (NM_IS_DEVICE_FACTORY (factory), FALSE); - - g_signal_emit (factory, signals[COMPONENT_ADDED], 0, component, &consumed); - return consumed; -} - static void nm_device_factory_get_supported_types (NMDeviceFactory *factory, const NMLinkType **out_link_types, @@ -182,16 +170,8 @@ nm_device_factory_class_init (NMDeviceFactoryClass *klass) signals[DEVICE_ADDED] = g_signal_new (NM_DEVICE_FACTORY_DEVICE_ADDED, G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_FIRST, - G_STRUCT_OFFSET (NMDeviceFactoryClass, device_added), - NULL, NULL, NULL, + 0, NULL, NULL, NULL, G_TYPE_NONE, 1, NM_TYPE_DEVICE); - - signals[COMPONENT_ADDED] = g_signal_new (NM_DEVICE_FACTORY_COMPONENT_ADDED, - G_OBJECT_CLASS_TYPE (object_class), - G_SIGNAL_RUN_LAST, - G_STRUCT_OFFSET (NMDeviceFactoryClass, component_added), - g_signal_accumulator_true_handled, NULL, NULL, - G_TYPE_BOOLEAN, 1, G_TYPE_OBJECT); } /*****************************************************************************/ diff --git a/src/devices/nm-device-factory.h b/src/devices/nm-device-factory.h index ac3bb1979c..11e83e9508 100644 --- a/src/devices/nm-device-factory.h +++ b/src/devices/nm-device-factory.h @@ -23,7 +23,6 @@ #define NM_IS_DEVICE_FACTORY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NM_TYPE_DEVICE_FACTORY)) #define NM_DEVICE_FACTORY_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), NM_TYPE_DEVICE_FACTORY, NMDeviceFactoryClass)) -#define NM_DEVICE_FACTORY_COMPONENT_ADDED "component-added" #define NM_DEVICE_FACTORY_DEVICE_ADDED "device-added" typedef struct { @@ -118,36 +117,6 @@ typedef struct { NMConnection *connection, gboolean *out_ignore); - /* Signals */ - - /** - * device_added: - * @factory: the #NMDeviceFactory - * @device: the new #NMDevice subclass - * - * The factory emits this signal if it finds a new device by itself. - */ - void (*device_added) (NMDeviceFactory *factory, NMDevice *device); - - /** - * component_added: - * @factory: the #NMDeviceFactory - * @component: a new component which existing devices may wish to claim - * - * The factory emits this signal when an appearance of some component - * native to it could be interesting to some of the already existing devices. - * The devices then indicate if they took interest in claiming the component. - * - * For example, the WWAN factory may indicate that a new modem is available, - * which an existing Bluetooth device may wish to claim. It emits a signal - * passing the modem instance around to see if any device claims it. - * If no device claims the component, the plugin is allowed to create a new - * #NMDevice instance for that component and emit the "device-added" signal. - * - * Returns: %TRUE if the component was claimed by a device, %FALSE if not - */ - gboolean (*component_added) (NMDeviceFactory *factory, GObject *component); - } NMDeviceFactoryClass; GType nm_device_factory_get_type (void); @@ -189,10 +158,6 @@ NMDevice * nm_device_factory_create_device (NMDeviceFactory *factory, gboolean *out_ignore, GError **error); -/* For use by implementations */ -gboolean nm_device_factory_emit_component_added (NMDeviceFactory *factory, - GObject *component); - #define NM_DEVICE_FACTORY_DECLARE_LINK_TYPES(...) \ { static NMLinkType const _link_types_declared[] = { __VA_ARGS__, NM_LINK_TYPE_NONE }; _link_types = _link_types_declared; } #define NM_DEVICE_FACTORY_DECLARE_SETTING_TYPES(...) \ diff --git a/src/devices/nm-device.c b/src/devices/nm-device.c index a30f588b27..bcbf5c27cd 100644 --- a/src/devices/nm-device.c +++ b/src/devices/nm-device.c @@ -4785,42 +4785,24 @@ nm_device_unrealize (NMDevice *self, gboolean remove_resources, GError **error) return TRUE; } -/** - * nm_device_notify_component_added(): - * @self: the #NMDevice - * @component: the component being added by a plugin - * - * Called by the manager to notify the device that a new component has - * been found. The device implementation should return %TRUE if it - * wishes to claim the component, or %FALSE if it cannot. - * - * Returns: %TRUE to claim the component, %FALSE if the component cannot be - * claimed. - */ -gboolean -nm_device_notify_component_added (NMDevice *self, GObject *component) +void +nm_device_notify_availability_maybe_changed (NMDevice *self) { - NMDeviceClass *klass; NMDevicePrivate *priv; - g_return_val_if_fail (NM_IS_DEVICE (self), FALSE); + g_return_if_fail (NM_IS_DEVICE (self)); priv = NM_DEVICE_GET_PRIVATE (self); - klass = NM_DEVICE_GET_CLASS (self); - - if (priv->state == NM_DEVICE_STATE_DISCONNECTED) { - /* A device could have stayed disconnected because it would - * want to register with a network server that now become - * available. */ - nm_device_recheck_available_connections (self); - if (g_hash_table_size (priv->available_connections) > 0) - nm_device_emit_recheck_auto_activate (self); - } - if (klass->component_added) - return klass->component_added (self, component); + if (priv->state != NM_DEVICE_STATE_DISCONNECTED) + return; - return FALSE; + /* A device could have stayed disconnected because it would + * want to register with a network server that now become + * available. */ + nm_device_recheck_available_connections (self); + if (g_hash_table_size (priv->available_connections) > 0) + nm_device_emit_recheck_auto_activate (self); } /** diff --git a/src/devices/nm-device.h b/src/devices/nm-device.h index 72c269c653..eaeac620d2 100644 --- a/src/devices/nm-device.h +++ b/src/devices/nm-device.h @@ -416,23 +416,6 @@ typedef struct _NMDeviceClass { int new_ifindex, NMDevice *new_parent); - /** - * component_added: - * @self: the #NMDevice - * @component: the component (device, modem, etc) which was added - * - * Notifies @self that a new component that a device might be interested - * in was detected by some device factory. It may include an object of - * %GObject subclass to help the devices decide whether it claims that - * particular object itself and the emitting factory should not. - * - * Returns: %TRUE if the component was claimed exclusively and no further - * devices should be notified of the new component. %FALSE to indicate - * that the component was not exclusively claimed and other devices should - * be notified. - */ - gboolean (* component_added) (NMDevice *self, GObject *component); - gboolean (* owns_iface) (NMDevice *self, const char *iface); NMConnection * (* new_default_connection) (NMDevice *self); @@ -809,7 +792,7 @@ gboolean nm_device_check_connection_available (NMDevice *device, const char *specific_object, GError **error); -gboolean nm_device_notify_component_added (NMDevice *device, GObject *component); +void nm_device_notify_availability_maybe_changed (NMDevice *self); gboolean nm_device_owns_iface (NMDevice *device, const char *iface); @@ -869,12 +852,22 @@ void nm_device_check_connectivity_cancel (NMDeviceConnectivityHandle *handle); NMConnectivityState nm_device_get_connectivity_state (NMDevice *self, int addr_family); typedef struct _NMBtVTableNetworkServer NMBtVTableNetworkServer; + +typedef void (*NMBtVTableRegisterCallback) (GError *error, + gpointer user_data); + struct _NMBtVTableNetworkServer { gboolean (*is_available) (const NMBtVTableNetworkServer *vtable, - const char *addr); + const char *addr, + NMDevice *device_accept_busy); + gboolean (*register_bridge) (const NMBtVTableNetworkServer *vtable, const char *addr, - NMDevice *device); + NMDevice *device, + GCancellable *cancellable, + NMBtVTableRegisterCallback callback, + gpointer callback_user_data, + GError **error); gboolean (*unregister_bridge) (const NMBtVTableNetworkServer *vtable, NMDevice *device); }; diff --git a/src/devices/wwan/nm-modem.c b/src/devices/wwan/nm-modem.c index 32490da1a6..e6174ccc49 100644 --- a/src/devices/wwan/nm-modem.c +++ b/src/devices/wwan/nm-modem.c @@ -1029,6 +1029,8 @@ nm_modem_act_stage1_prepare (NMModem *self, NMSecretAgentGetSecretsFlags flags = NM_SECRET_AGENT_GET_SECRETS_FLAG_ALLOW_INTERACTION; NMConnection *connection; + g_return_val_if_fail (NM_IS_ACT_REQUEST (req), NM_ACT_STAGE_RETURN_FAILURE); + if (priv->act_request) g_object_unref (priv->act_request); priv->act_request = g_object_ref (req); @@ -1066,8 +1068,11 @@ nm_modem_act_stage1_prepare (NMModem *self, void nm_modem_act_stage2_config (NMModem *self) { - NMModemPrivate *priv = NM_MODEM_GET_PRIVATE (self); + NMModemPrivate *priv; + g_return_if_fail (NM_IS_MODEM (self)); + + priv = NM_MODEM_GET_PRIVATE (self); /* Clear secrets tries counter since secrets were successfully used * already if we get here. */ diff --git a/src/devices/wwan/nm-wwan-factory.c b/src/devices/wwan/nm-wwan-factory.c index 7a93b47c7b..c81a8175bd 100644 --- a/src/devices/wwan/nm-wwan-factory.c +++ b/src/devices/wwan/nm-wwan-factory.c @@ -67,10 +67,6 @@ modem_added_cb (NMModemManager *manager, gs_unref_object NMDevice *device = NULL; const char *driver; - /* Do nothing if the modem was consumed by some other plugin */ - if (nm_device_factory_emit_component_added (NM_DEVICE_FACTORY (self), G_OBJECT (modem))) - return; - if (nm_modem_is_claimed (modem)) return; @@ -80,9 +76,10 @@ modem_added_cb (NMModemManager *manager, * it. The rfcomm port (and thus the modem) gets created automatically * by the Bluetooth code during the connection process. */ - if (driver && strstr (driver, "bluetooth")) { - nm_log_info (LOGD_MB, "ignoring modem '%s' (no associated Bluetooth device)", - nm_modem_get_control_port (modem)); + if ( driver + && strstr (driver, "bluetooth")) { + nm_log_dbg (LOGD_MB, "WWAN factory ignores bluetooth modem '%s' which should be handled by bluetooth plugin", + nm_modem_get_control_port (modem)); return; } diff --git a/src/nm-manager.c b/src/nm-manager.c index 6a01426753..b21d39a015 100644 --- a/src/nm-manager.c +++ b/src/nm-manager.c @@ -3188,22 +3188,6 @@ factory_device_added_cb (NMDeviceFactory *factory, } } -static gboolean -factory_component_added_cb (NMDeviceFactory *factory, - GObject *component, - gpointer user_data) -{ - NMManager *self = user_data; - NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self); - NMDevice *device; - - c_list_for_each_entry (device, &priv->devices_lst_head, devices_lst) { - if (nm_device_notify_component_added (device, component)) - return TRUE; - } - return FALSE; -} - static void _register_device_factory (NMDeviceFactory *factory, gpointer user_data) { @@ -3213,10 +3197,18 @@ _register_device_factory (NMDeviceFactory *factory, gpointer user_data) NM_DEVICE_FACTORY_DEVICE_ADDED, G_CALLBACK (factory_device_added_cb), self); - g_signal_connect (factory, - NM_DEVICE_FACTORY_COMPONENT_ADDED, - G_CALLBACK (factory_component_added_cb), - self); +} + +/*****************************************************************************/ + +void +nm_manager_notify_device_availibility_maybe_changed (NMManager *self) +{ + NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self); + NMDevice *device; + + c_list_for_each_entry (device, &priv->devices_lst_head, devices_lst) + nm_device_notify_availability_maybe_changed (device); } /*****************************************************************************/ diff --git a/src/nm-manager.h b/src/nm-manager.h index bedd004e81..187ce72863 100644 --- a/src/nm-manager.h +++ b/src/nm-manager.h @@ -191,4 +191,6 @@ void nm_manager_dbus_set_property_handle (NMDBusObject *obj, NMMetered nm_manager_get_metered (NMManager *self); +void nm_manager_notify_device_availibility_maybe_changed (NMManager *self); + #endif /* __NETWORKMANAGER_MANAGER_H__ */ |