// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (C) 2013 - 2014 Red Hat, Inc.
*/
#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-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"
/*****************************************************************************/
#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;
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;
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;
struct _NMBluezManager {
NMDeviceFactory parent;
NMBluezManagerPrivate _priv;
};
struct _NMBluezManagerClass {
NMDeviceFactoryClass parent;
};
G_DEFINE_TYPE (NMBluezManager, nm_bluez_manager, NM_TYPE_DEVICE_FACTORY);
#define NM_BLUEZ_MANAGER_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMBluezManager, NM_IS_BLUEZ_MANAGER)
/*****************************************************************************/
NM_DEVICE_FACTORY_DECLARE_TYPES (
NM_DEVICE_FACTORY_DECLARE_LINK_TYPES (NM_LINK_TYPE_BNEP)
NM_DEVICE_FACTORY_DECLARE_SETTING_TYPES (NM_SETTING_BLUETOOTH_SETTING_NAME)
)
G_MODULE_EXPORT NMDeviceFactory *
nm_device_factory_create (GError **error)
{
return (NMDeviceFactory *) g_object_new (NM_TYPE_BLUEZ_MANAGER, NULL);
}
/*****************************************************************************/
#define _NMLOG_DOMAIN LOGD_BT
#define _NMLOG(level, ...) __NMLOG_DEFAULT (level, _NMLOG_DOMAIN, "bluez", __VA_ARGS__)
/*****************************************************************************/
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 (_nm_utils_ascii_str_to_int64 (s_part1, 16, 0, G_MAXINT, -1)) {
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_error 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_error 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);
}
/*****************************************************************************/
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;
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;
#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)
{
_conn_track_update (self, sett_conn, TRUE, NULL, NULL, NULL);
}
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 *
_network_server_get_bluez_manager (const NMBtVTableNetworkServer *vtable_network_server)
{
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);
return self;
}
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
_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))
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_shutdown_wait_obj_register_cancellable_full (cancellable,
g_strdup_printf ("bt-unregister-nap[%s]", bzobj->object_path),
TRUE);
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 (r_req_data) {
nm_clear_g_cancellable (&r_req_data->int_cancellable);
nm_utils_invoke_on_idle (r_req_data->ext_cancellable,
_network_server_unregister_bridge_complete_on_idle_cb,
nm_utils_user_data_pack (r_req_data, g_strdup (reason)));
}
_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
_network_server_process_change (BzDBusObj *bzobj,
gboolean *out_emit_device_availability_changed)
{
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);
}
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);
}
}
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;
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
_device_process_change (BzDBusObj *bzobj)
{
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
_process_change_idle_all (NMBluezManager *self,
gboolean *out_emit_device_availability_changed)
{
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);
_LOG_bzobj (bzobj, "before-processing");
_device_process_change (bzobj);
_network_server_process_change (bzobj, out_emit_device_availability_changed);
_LOG_bzobj (bzobj, "after-processing");
_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
_process_change_idle_schedule (NMBluezManager *self,
BzDBusObj *bzobj)
{
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
_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;
}
}
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 (GDBusConnection *connection,
const char *sender_name,
const char *arg_object_path,
const char *interface_name,
const char *signal_name,
GVariant *parameters,
gpointer user_data)
{
NMBluezManager *self = user_data;
NMBluezManagerPrivate *priv = NM_BLUEZ_MANAGER_GET_PRIVATE (self);
BzDBusObj *bzobj = NULL;
gboolean changed;
nm_assert (nm_streq0 (interface_name, DBUS_INTERFACE_OBJECT_MANAGER));
if (priv->get_managed_objects_cancellable) {
/* we still wait for the initial GetManagedObjects(). Ignore the event. */
return;
}
if (nm_streq (signal_name, "InterfacesAdded")) {
gs_unref_variant GVariant *interfaces_and_properties = NULL;
const char *object_path;
if (!g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(oa{sa{sv}})")))
return;
g_variant_get (parameters,
"(&o@a{sa{sv}})",
&object_path,
&interfaces_and_properties);
_dbus_handle_interface_added (self, object_path, interfaces_and_properties, FALSE);
return;
}
if (nm_streq (signal_name, "InterfacesRemoved")) {
gs_free const char **interfaces = NULL;
const char *object_path;
if (!g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(oas)")))
return;
g_variant_get (parameters,
"(&o^a&s)",
&object_path,
&interfaces);
changed = _dbus_handle_interface_removed (self, object_path, &bzobj, interfaces);
if (changed)
_dbus_process_changes (self, bzobj, "dbus-iface-removed");
return;
}
}
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
_dbus_get_managed_objects_cb (GVariant *result,
GError *error,
gpointer user_data)
{
NMBluezManager *self;
NMBluezManagerPrivate *priv;
GVariantIter iter;
const char *object_path;
GVariant *ifaces;
if ( !result
&& nm_utils_error_is_cancelled (error))
return;
self = user_data;
priv = NM_BLUEZ_MANAGER_GET_PRIVATE (self);
g_clear_object (&priv->get_managed_objects_cancellable);
if (!result) {
_LOGT ("initial GetManagedObjects() call failed: %s", error->message);
_cleanup_for_name_owner (self);
return;
}
_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
_cleanup_for_name_owner (NMBluezManager *self)
{
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 (nm_streq0 (priv->name_owner, owner))
return;
_cleanup_for_name_owner (self);
if (!owner)
return;
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,
NULL,
_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);
}
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;
if (!g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(sss)")))
return;
g_variant_get (parameters,
"(&s&s&s)",
NULL,
NULL,
&new_owner);
name_owner_changed (self, new_owner);
}
static void
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);
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);
_cleanup_for_name_owner (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)
{
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))
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))
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))
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,
const NMPlatformLink *plink,
NMConnection *connection,
gboolean *out_ignore)
{
*out_ignore = TRUE;
g_return_val_if_fail (plink->type == NM_LINK_TYPE_BNEP, NULL);
return NULL;
}
static gboolean
match_connection (NMDeviceFactory *factory,
NMConnection *connection)
{
const char *type = nm_connection_get_connection_type (connection);
nm_assert (nm_streq (type, NM_SETTING_BLUETOOTH_SETTING_NAME));
if (_nm_connection_get_setting_bluetooth_for_nap (connection))
return FALSE; /* handled by the bridge factory */
return TRUE;
}
/*****************************************************************************/
static void
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
dispose (GObject *object)
{
NMBluezManager *self = NM_BLUEZ_MANAGER (object);
NMBluezManagerPrivate *priv = NM_BLUEZ_MANAGER_GET_PRIVATE (self);
/* 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);
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
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;
factory_class->get_supported_types = get_supported_types;
factory_class->create_device = create_device;
factory_class->match_connection = match_connection;
factory_class->start = start;
}