summaryrefslogtreecommitdiff
path: root/src/devices/bluetooth/nm-bluez-manager.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/devices/bluetooth/nm-bluez-manager.c')
-rw-r--r--src/devices/bluetooth/nm-bluez-manager.c2835
1 files changed, 2659 insertions, 176 deletions
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;
}