bluetoothd: device cache

目录结构

关于蓝牙数据的存储,在bluez的doc目录下有一个文件(settings-storage.txt)作出了说明。

Adapter and remote device info are read form the storage during object initialization. Write to storage is performed immediately on every value
change.

Default storage directory is /var/lib/bluetooth. This can be adjusted by the –localstatedir configure switch. Default is –localstatedir=/var.

翻译:在对象初始化期间,从存储中读取适配器和远程设备信息。每次值更改时都会立即写入存储。默认存储目录为 /var/lib/bluetooth。可以通过 –localstatedir 配置开关进行调整。默认值为 --localstatedir=/var

首次运行bluetoothd的存储目录结构:

1
2
3
4
5
$ tree bluetooth
bluetooth/
└── 40:80:E1:50:1D:47
├── attributes
└── settings

注意:40:80:E1:50:1D:47是本地蓝牙适配器的MAC地址作为目录名称。若本地存在多个蓝牙适配器,那么就有多个这样的目录。

执行扫描、配对、连接后的存储目录结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
bluetooth/
└── 40:80:E1:50:1D:47
├── attributes
├── cache
│   ├── 08:F0:B6:58:2E:90
│   ├── 18:C2:3C:31:3C:3A
│   ├── 23:07:04:00:03:C0
│   ├── 3A:29:EC:03:27:D0
│   ├── 44:17:93:CD:E4:36
│   ├── 60:F4:3A:A6:4D:E1
│   ├── 64:68:76:FC:B5:E3
│   ├── 88:4A:18:88:9F:F5
│   ├── 8C:F6:81:FC:9D:F1
│   ├── 90:F0:52:BE:DC:6E
│   ├── 98:8D:46:1A:60:20
│   ├── AC:D8:29:09:18:B6
│   ├── B4:8C:9D:06:D4:83
│   ├── B8:14:4D:73:52:99
│   ├── C8:94:02:E1:6D:C4
│   ├── D0:62:2C:13:2E:8F
│   ├── E4:19:C1:E4:35:21
│   └── F4:4E:FD:7A:71:09
├── F4:4E:FD:7A:71:09
│   ├── attributes
│   └── info
└── settings

注意:执行扫描操作后,cache 目录就被创建,同时扫描到的蓝牙设备也会以其MAC地址名称作为文件名。若配对、连接指定蓝牙设备成功后,则会创建以其MAC地址名称作为目录名,而且该目录下也会创建 attribute 和 info两个文件。

cache目录下蓝牙设备文件被创建的位置,通过device_store_cached_name()函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
void device_store_cached_name(struct btd_device *dev, const char *name)
{
char filename[PATH_MAX];
char d_addr[18];
GKeyFile *key_file;
GError *gerr = NULL;
char *data;
char *data_old;
gsize length = 0;
gsize length_old = 0;

if (device_address_is_private(dev)) {
DBG("Can't store name for private addressed device %s",
dev->path);
return;
}

ba2str(&dev->bdaddr, d_addr);
create_filename(filename, PATH_MAX, "/%s/cache/%s",
btd_adapter_get_storage_dir(dev->adapter), d_addr);
create_file(filename, 0600);

key_file = g_key_file_new();
if (!g_key_file_load_from_file(key_file, filename, 0, &gerr)) {
error("Unable to load key file from %s: (%s)", filename,
gerr->message);
g_clear_error(&gerr);
}

data_old = g_key_file_to_data(key_file, &length_old, NULL);

g_key_file_set_string(key_file, "General", "Name", name);

data = g_key_file_to_data(key_file, &length, NULL);

if ((length != length_old) || (memcmp(data, data_old, length))) {
if (!g_file_set_contents(filename, data, length, &gerr)) {
error("Unable set contents for %s: (%s)", filename,
gerr->message);
g_clear_error(&gerr);
}
}
g_free(data);
g_free(data_old);

g_key_file_free(key_file);
}

settings文件

settings文件包含了适配器的一个组的信息,例如:

1
2
3
[General]
Discoverable=false
Alias=SCO_AUDIO1
  • Alias:适配器的别名,默认值是主机名
  • Discoverable:适配器的可被发现的属性

settings文件以及内容被创建位置,通过 src/adapter.c 中的store_adapter_info()函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
static void store_adapter_info(struct btd_adapter *adapter)
{
GKeyFile *key_file;
GError *gerr = NULL;
char filename[PATH_MAX];
char *str;
gsize length = 0;
gboolean discoverable;

key_file = g_key_file_new();

if (adapter->pairable_timeout != btd_opts.pairto)
g_key_file_set_integer(key_file, "General", "PairableTimeout",
adapter->pairable_timeout);

if ((adapter->current_settings & MGMT_SETTING_DISCOVERABLE) &&
!adapter->discoverable_timeout)
discoverable = TRUE;
else
discoverable = FALSE;

g_key_file_set_boolean(key_file, "General", "Discoverable",
discoverable);

if (adapter->discoverable_timeout != btd_opts.discovto)
g_key_file_set_integer(key_file, "General",
"DiscoverableTimeout",
adapter->discoverable_timeout);

if (adapter->stored_alias)
g_key_file_set_string(key_file, "General", "Alias",
adapter->stored_alias);

create_filename(filename, PATH_MAX, "/%s/settings",
btd_adapter_get_storage_dir(adapter));
create_file(filename, 0600);

str = g_key_file_to_data(key_file, &length, NULL);
if (!g_file_set_contents(filename, str, length, &gerr)) {
error("Unable set contents for %s: (%s)", filename,
gerr->message);
g_error_free(gerr);
}
g_free(str);

g_key_file_free(key_file);
}

info文件

info文件包含了与蓝牙设备关联的多组信息,例如:

1
2
3
4
5
6
7
8
9
10
11
12
[General]
Name=JBL ROCK
Class=0x240404
SupportedTechnologies=BR/EDR;
Trusted=false
Blocked=false
Services=0000110b-0000-1000-8000-00805f9b34fb;0000110c-0000-1000-8000-00805f9b34fb;0000110e-0000-1000-8000-00805f9b34fb;0000111e-0000-1000-8000-00805f9b34fb;

[LinkKey]
Key=2BB06C500CC2926271D2B31687931BFF
Type=4
PINLength=0

info文件的创建位置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
static void store_link_key(struct btd_adapter *adapter,
struct btd_device *device, const uint8_t *key,
uint8_t type, uint8_t pin_length)
{
char device_addr[18];
char filename[PATH_MAX];
GKeyFile *key_file;
GError *gerr = NULL;
gsize length = 0;
char key_str[33];
char *str;
int i;

ba2str(device_get_address(device), device_addr);

create_filename(filename, PATH_MAX, "/%s/%s/info",
btd_adapter_get_storage_dir(adapter), device_addr);
create_file(filename, 0600);

key_file = g_key_file_new();
if (!g_key_file_load_from_file(key_file, filename, 0, &gerr)) {
error("Unable to load key file from %s: (%s)", filename,
gerr->message);
g_error_free(gerr);
g_key_file_free(key_file);
return;
}

for (i = 0; i < 16; i++)
sprintf(key_str + (i * 2), "%2.2X", key[i]);

g_key_file_set_string(key_file, "LinkKey", "Key", key_str);

g_key_file_set_integer(key_file, "LinkKey", "Type", type);
g_key_file_set_integer(key_file, "LinkKey", "PINLength", pin_length);

str = g_key_file_to_data(key_file, &length, NULL);
if (!g_file_set_contents(filename, str, length, &gerr)) {
error("Unable set contents for %s: (%s)", filename,
gerr->message);
g_error_free(gerr);
}
g_free(str);

g_key_file_free(key_file);
}

方法实现

org.bluez.Adapter1 接口文档中提供了三个接口函数:

  • StartDiscovery()
  • StopDiscovery()
  • RemoveDevice()

StartDiscovery() 函数被执行时,cache目录下就会有新的蓝牙设备文件被创建,而当 StopDiscovery() 函数被执行时,cache目录下就会有蓝牙设备文件被删除,RemoveDevice() 函数被执行时,<controller address> 目录下有已配对或已连接过的蓝牙设备文件夹会被删除。

注意:当蓝牙设备被自动移除时,cache目录的蓝牙设备文件部分还是保留,为什么会保留呢?

src/adapter.c 关于适配器相关的方法实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
// 适配器注册的方法
static const GDBusMethodTable adapter_methods[] = {
{ GDBUS_ASYNC_METHOD("StartDiscovery", NULL, NULL, start_discovery) },
{ GDBUS_METHOD("SetDiscoveryFilter",
GDBUS_ARGS({ "properties", "a{sv}" }), NULL,
set_discovery_filter) },
{ GDBUS_ASYNC_METHOD("StopDiscovery", NULL, NULL, stop_discovery) },
{ GDBUS_ASYNC_METHOD("RemoveDevice",
GDBUS_ARGS({ "device", "o" }), NULL, remove_device) },
{ GDBUS_METHOD("GetDiscoveryFilters", NULL,
GDBUS_ARGS({ "filters", "as" }),
get_discovery_filters) },
{ GDBUS_EXPERIMENTAL_ASYNC_METHOD("ConnectDevice",
GDBUS_ARGS({ "properties", "a{sv}" }), NULL,
connect_device) },
{ }
};

// 适配器注册的属性
static const GDBusPropertyTable adapter_properties[] = {
{ "Address", "s", property_get_address },
{ "AddressType", "s", property_get_address_type },
{ "Name", "s", property_get_name },
{ "Alias", "s", property_get_alias, property_set_alias },
{ "Class", "u", property_get_class },
{ "Powered", "b", property_get_powered, property_set_powered },
{ "PowerState", "s", property_get_power_state },
{ "Discoverable", "b", property_get_discoverable,
property_set_discoverable },
{ "DiscoverableTimeout", "u", property_get_discoverable_timeout,
property_set_discoverable_timeout },
{ "Pairable", "b", property_get_pairable, property_set_pairable },
{ "PairableTimeout", "u", property_get_pairable_timeout,
property_set_pairable_timeout },
{ "Discovering", "b", property_get_discovering },
{ "UUIDs", "as", property_get_uuids },
{ "Modalias", "s", property_get_modalias, NULL,
property_exists_modalias },
{ "Roles", "as", property_get_roles },
{ "ExperimentalFeatures", "as", property_get_experimental, NULL,
property_experimental_exists },
{ "Manufacturer", "q", property_get_manufacturer },
{ "Version", "y", property_get_version },
{ }
};

StartDiscovery方法的实现

在src/adapter.c给出了StartDiscovery方法的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
static DBusMessage *start_discovery(DBusConnection *conn,
DBusMessage *msg, void *user_data)
{
struct btd_adapter *adapter = user_data;
const char *sender = dbus_message_get_sender(msg);
struct discovery_client *client;
bool is_discovering;
int err;

DBG("sender %s", sender);

if (!btd_adapter_get_powered(adapter))
return btd_error_not_ready(msg);

is_discovering = get_discovery_client(adapter, sender, &client);

/*
* Every client can only start one discovery, if the client
* already started a discovery then return an error.
* 每一个客户端仅能启动一次扫描,如果客户端早已启动了扫描,那么此时返回一个错误
*/
if (is_discovering)
return btd_error_busy(msg);

/*
* If there was pre-set filter, just reconnect it to discovery_list,
* and trigger scan.
* 如果有预设置的过滤器,那么只是到扫描列表去重连它并触发扫描
*/
if (client) {
if (client->msg)
return btd_error_busy(msg);

adapter->set_filter_list = g_slist_remove(
adapter->set_filter_list, client);
adapter->discovery_list = g_slist_prepend(
adapter->discovery_list, client);

goto done;
}

client = g_new0(struct discovery_client, 1);

client->adapter = adapter;
client->owner = g_strdup(sender);
client->discovery_filter = NULL;
client->watch = g_dbus_add_disconnect_watch(dbus_conn, sender,
discovery_disconnect, client,
NULL);
adapter->discovery_list = g_slist_prepend(adapter->discovery_list,
client);

done:
/*
* Just trigger the discovery here. In case an already running
* discovery in idle phase exists, it will be restarted right
* away.
* 只需在此处触发扫描。如果存在在空闲段运行的发现,它将立即重新启动扫描。
*/
err = update_discovery_filter(adapter);
if (!err)
return dbus_message_new_method_return(msg);

/* If the discovery has to be started wait it complete to reply */
if (err == -EINPROGRESS) {
client->msg = dbus_message_ref(msg);
adapter->client = client;
return NULL;
}

return btd_error_failed(msg, strerror(-err));
}

RemoveDevice方法的实现

在src/adapter.c给出了RemoveDevice方法的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// RemoveDevice方法的回调函数
static DBusMessage *remove_device(DBusConnection *conn,
DBusMessage *msg, void *user_data)
{
struct btd_adapter *adapter = user_data;
struct btd_device *device;
const char *path;
GSList *list;

// RemoveDevice方法传递的参数类型是一个路径对象
if (dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &path,
DBUS_TYPE_INVALID) == FALSE)
return btd_error_invalid_args(msg);

// 在链表中查找是否存在目标蓝牙设备
list = g_slist_find_custom(adapter->devices, path, device_path_cmp);
if (!list)
return btd_error_does_not_exist(msg);

// 判断适配器Powered是否为TRUE,若为FALSE,则执行花括号里面的内容。
if (!btd_adapter_get_powered(adapter))
return btd_error_not_ready(msg);

device = list->data;

btd_device_set_temporary(device, true);

// 判断Device是否connected为TRUE,若为FALSE,则执行花括号里面的内容。
if (!btd_device_is_connected(device))
{
// 适配器移除蓝牙设备,然后构造一条消息作为方法调用的应答
btd_adapter_remove_device(adapter, device);
return dbus_message_new_method_return(msg);
}

// 请求断开目标蓝牙设备的连接
device_request_disconnect(device, msg);

return NULL;
}

StopDiscovery方法的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// StopDiscovery方法的回调函数
static DBusMessage *stop_discovery(DBusConnection *conn,
DBusMessage *msg, void *user_data)
{
struct btd_adapter *adapter = user_data;
const char *sender = dbus_message_get_sender(msg);
struct discovery_client *client;
GSList *list;
int err;

DBG("sender %s", sender);

// 检查适配器的Powered是否为TRUE
if (!btd_adapter_get_powered(adapter))
return btd_error_not_ready(msg);
// 检查发送者是否在发现列表中,即该发送者之前是否发送过StartDiscovery方法
list = g_slist_find_custom(adapter->discovery_list, sender,
compare_sender);
if (!list)
return btd_error_failed(msg, "No discovery started");

client = list->data;

// 检查客户端是否有了待处理的消息
if (client->msg)
return btd_error_busy(msg);
// 调用扫描停止函数
err = discovery_stop(client);
switch (err) {
case 0:
return dbus_message_new_method_return(msg);
case -EINPROGRESS:
client->msg = dbus_message_ref(msg);
adapter->client = client;
return NULL;
default:
return btd_error_failed(msg, strerror(-err));
}
}

临时定时器

在/src/main.conf文件中,有关于扫描到的设备临时(temporary)时间定义

1
2
3
4
5
6
[General]

# How long to keep temporary devices around
# The value is in seconds. Default is 30.
# 0 = disable timer, i.e. never keep temporary devices
#TemporaryTimeout = 30

在/src/main.c文件中,关于临时(temporary)时间也有宏定义:

1
2
3
4
#define DEFAULT_PAIRABLE_TIMEOUT           0 /* disabled */
#define DEFAULT_DISCOVERABLE_TIMEOUT 180 /* 3 minutes */
#define DEFAULT_TEMPORARY_TIMEOUT 30 /* 30 seconds */
#define DEFAULT_NAME_REQUEST_RETRY_DELAY 300 /* 5 minutes */

/src/device.c源文件关于临时(temporary)定时器的记录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
static bool device_disappeared(gpointer user_data)
{
struct btd_device *dev = user_data;

/* If there are services connected restart the timer to give more time
* for the service to either complete the connection or disconnect.
*/
if (device_service_connected(dev))
return TRUE;

dev->temporary_timer = 0;
// here adapter remove device
btd_adapter_remove_device(dev->adapter, dev);

return FALSE;
}

static void set_temporary_timer(struct btd_device *dev, unsigned int timeout)
{
clear_temporary_timer(dev);

if (!timeout)
return;

dev->temporary_timer = timeout_add_seconds(timeout, device_disappeared,
dev, NULL);
}

void btd_device_set_temporary(struct btd_device *device, bool temporary)
{
if (!device)
return;

if (device->temporary == temporary)
return;

if (device_address_is_private(device))
return;

DBG("temporary %d", temporary);

device->temporary = temporary;

if (temporary)
{
if (device->bredr)
adapter_accept_list_remove(device->adapter, device);
adapter_connect_list_remove(device->adapter, device);
if (device->auto_connect) {
device->disable_auto_connect = TRUE;
device_set_auto_connect(device, FALSE);
}
set_temporary_timer(device, btd_opts.tmpto);// here
return;
}
else
clear_temporary_timer(device);

if (device->bredr)
adapter_accept_list_add(device->adapter, device);

store_device_info(device);

/* attributes were not stored when resolved if device was temporary */
if (device->bdaddr_type != BDADDR_BREDR &&
device->le_state.svc_resolved &&
g_slist_length(device->primaries) != 0)
store_services(device);
}

btd_device_set_temporary()函数的参数temporary的值为 true时,device将被标记为临时设备,并通过 set_temporary_timer(device, btd_opts.tmpto) 启动了一个临时的定时器。

/src/adapter.c关于临时(temporary)定时器的使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
void btd_adapter_remove_device(struct btd_adapter *adapter,
struct btd_device *dev)
{
GList *l;

adapter->connect_list = g_slist_remove(adapter->connect_list, dev);

adapter_remove_device(adapter, dev);
btd_adv_monitor_device_remove(adapter->adv_monitor_manager, dev);

adapter->discovery_found = g_slist_remove(adapter->discovery_found,
dev);

adapter->connections = g_slist_remove(adapter->connections, dev);

if (adapter->connect_le == dev)
adapter->connect_le = NULL;

l = adapter->auths->head;
while (l != NULL) {
struct service_auth *auth = l->data;
GList *next = g_list_next(l);

if (auth->device != dev) {
l = next;
continue;
}

g_queue_delete_link(adapter->auths, l);
l = next;

service_auth_cancel(auth);
}

device_remove(dev, TRUE);
}

static void discovery_cleanup(struct btd_adapter *adapter, int timeout)
{
GSList *l, *next;

adapter->discovery_type = 0x00;

if (adapter->discovery_idle_timeout > 0) {
timeout_remove(adapter->discovery_idle_timeout);
adapter->discovery_idle_timeout = 0;
}

g_slist_free_full(adapter->discovery_found,
invalidate_rssi_and_tx_power);
adapter->discovery_found = NULL;

if (!adapter->devices)
return;

for (l = adapter->devices; l != NULL; l = next) {
struct btd_device *dev = l->data;

next = g_slist_next(l);

if (device_is_temporary(dev) && !device_is_connectable(dev)
&& !btd_device_is_connected(dev))
btd_adapter_remove_device(adapter, dev); // here
}
}

static void remove_temporary_devices(struct btd_adapter *adapter)
{
GSList *l, *next;

for (l = adapter->devices; l; l = next) {
struct btd_device *dev = l->data;

next = g_slist_next(l);
if (device_is_temporary(dev))
btd_adapter_remove_device(adapter, dev); // here
}
}

static DBusMessage *remove_device(DBusConnection *conn,
DBusMessage *msg, void *user_data)
{
struct btd_adapter *adapter = user_data;
struct btd_device *device;
const char *path;
GSList *list;

if (dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &path,
DBUS_TYPE_INVALID) == FALSE)
return btd_error_invalid_args(msg);

list = g_slist_find_custom(adapter->devices, path, device_path_cmp);
if (!list)
return btd_error_does_not_exist(msg);

if (!btd_adapter_get_powered(adapter))
return btd_error_not_ready(msg);

device = list->data;

btd_device_set_temporary(device, true);

if (!btd_device_is_connected(device))
{
// here adapter remove device
btd_adapter_remove_device(adapter, device);
return dbus_message_new_method_return(msg);
}

device_request_disconnect(device, msg);

return NULL;
}

但是最终的问题还是没有得到解决:bluetoothd程序接收到客户端(例如:bluetoothctl)的请求: StartDiscovery 方法时,蓝牙设备被发现后,通过什么样的方式通知了客户端呢?同样的,客户端发送 RemoveDevice 方法时,bluetoothd又是通过什么方式告知客户端?最后一点,当附近设备被扫描到后,会作为一个临时设备,当临时时间最大时间到达后,在哪里执行删除临时设备操作,以及删除临时设备后,以什么方式通知客户端?

疑难杂症

通过使用bluez提供的gdbus接口编写了一个客户端(从这边文章可以得知:{% post_link 'bluetoothctl-client-tool' %} ),并且为了测试连接的稳定性编译了一个自动化测试函数:

  • 第一步:bt_open() 打开蓝牙并进行初始化操作
  • 第二步:bt_start_discovery() 开始执行扫描操作
  • 第三步:进入while() 循环体,完成指定的连接次数
    • 等待目标蓝牙设备被扫描到
    • 连接目标蓝牙设备
    • 移除目标蓝牙设备,并进入下一轮循环体
  • 第四步:退出while() 循环体,bt_cancel_discovery() 停止扫描操作
  • 第五步:bt_close() 关闭蓝牙并释放相关的资源

执行测试次数为100次,总会出现那么几次以上的连接超时失败:Timeout was reached。查看日志,发现执行connect操作后,立即出现了删除临时设备的操作(包括目标蓝牙设备),接下来就是连接超时报错了。

如果没有扫描到目标蓝牙设备,直接去连接,则会报这个错误:

1
GDBus.Error:org.freedesktop.DBus.Error.UnknownObject: Method "Connect" with signature "" on interface "org.bluez.Device1" doesn't exist

所以关键的问题是找到执行connect操作会不会导致bluetoothd进程执行清理临时设备操作!

查询 org.bluez.Device1 的 Connect 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
static const GDBusMethodTable device_methods[] = {
{ GDBUS_ASYNC_METHOD("Disconnect", NULL, NULL, dev_disconnect) },
{ GDBUS_ASYNC_METHOD("Connect", NULL, NULL, dev_connect) },
{ GDBUS_ASYNC_METHOD("ConnectProfile", GDBUS_ARGS({ "UUID", "s" }),
NULL, connect_profile) },
{ GDBUS_ASYNC_METHOD("DisconnectProfile", GDBUS_ARGS({ "UUID", "s" }),
NULL, disconnect_profile) },
{ GDBUS_ASYNC_METHOD("Pair", NULL, NULL, pair_device) },
{ GDBUS_METHOD("CancelPairing", NULL, NULL, cancel_pairing) },
{ }
};

// Connect方法的回调函数
static DBusMessage *dev_connect(DBusConnection *conn, DBusMessage *msg,
void *user_data)
{
struct btd_device *dev = user_data;
uint8_t bdaddr_type;

if (dev->bredr_state.connected)
{
/*
* Check if services have been resolved and there is at least
* one connected before switching to connect LE.
*/
if (dev->bredr_state.svc_resolved &&
find_service_with_state(dev->services,
BTD_SERVICE_STATE_CONNECTED))
bdaddr_type = dev->bdaddr_type;
else
bdaddr_type = BDADDR_BREDR;
}
else if (dev->le_state.connected && dev->bredr)
bdaddr_type = BDADDR_BREDR;
else
bdaddr_type = select_conn_bearer(dev);

if (bdaddr_type != BDADDR_BREDR) {
int err;

if (dev->le_state.connected)
return dbus_message_new_method_return(msg);

btd_device_set_temporary(dev, false);// 与临时设备相关的函数,移除缓存定时器

if (dev->disable_auto_connect) {
dev->disable_auto_connect = FALSE;
device_set_auto_connect(dev, TRUE);
}

err = device_connect_le(dev);
if (err < 0)
return btd_error_failed(msg, strerror(-err));

dev->connect = dbus_message_ref(msg);

return NULL;
}

return connect_profiles(dev, bdaddr_type, msg, NULL);
}

虽然不能100%确认设备删除的时间点,但是我查到了相关调用关系,最终设备被移除时一定会调用函数 g_dbus_unregister_interface() ,那么我需要向上找出关于这个函数被调用的位置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# src/adapter.c
# 第一个调用方向
g_dbus_unregister_interface() --> btd_device_unref() --> device_remove()
--> btd_adapter_remove_device() --> remove_temporary_devices()

# 第二个调用方向
g_dbus_unregister_interface() --> btd_device_unref() --> device_remove()
--> btd_adapter_remove_device() --> discovery_cleanup() --> discovery_remove()
--> start_discovery_complete() --> start_discovery_timeout()
--> trigger_start_discovery() --> discovering_callback()

# 第三个调用方向
g_dbus_unregister_interface() --> btd_device_unref() --> device_remove()
--> btd_adapter_remove_device() --> remove_device()

# src/device.c中btd_adapter_remove_device()的调用关系
btd_adapter_remove_device() --> device_disappeared() --> set_temporary_timer()
--> btd_device_set_temporary() --> dev_connect()

当然,最好将bluetoothd的日志保存到文件,但是也有一个烦恼的问题,就是日志文件太大了,动不动就是10M以上的大小,排查很困难。

1
/usr/libexec/bluetooth/bluetoothd -d -n > bluetoothd.log 2>&1 &