Implement storing of link keys in runtime memory

The management interface will enable storing link keys in runtime memory
in the kernel. This patch makes the adapter interface match this model
and adds storing of link keys into hciops.
diff --git a/plugins/hciops.c b/plugins/hciops.c
index cc285d1..3533f9b 100644
--- a/plugins/hciops.c
+++ b/plugins/hciops.c
@@ -90,6 +90,8 @@
 
 	GIOChannel *io;
 	guint watch_id;
+
+	GSList *keys;
 	int pin_length;
 } *devs = NULL;
 
@@ -534,12 +536,27 @@
 
 /* Start of HCI event callbacks */
 
-static int get_handle(int index, bdaddr_t *dba, uint16_t *handle)
+static int disconnect_handle(int index, uint16_t handle, uint8_t reason)
+{
+	struct dev_info *dev = &devs[index];
+	disconnect_cp cp;
+
+	memset(&cp, 0, sizeof(cp)); cp.handle = htobs(handle);
+	cp.reason = reason;
+
+	if (hci_send_cmd(dev->sk, OGF_LINK_CTL, OCF_DISCONNECT,
+						DISCONNECT_CP_SIZE, &cp) < 0)
+		return -errno;
+
+	return 0;
+}
+
+static int disconnect_addr(int index, bdaddr_t *dba, uint8_t reason)
 {
 	struct dev_info *dev = &devs[index];
 	struct hci_conn_list_req *cl;
 	struct hci_conn_info *ci;
-	int i;
+	int i, err;
 
 	cl = g_malloc0(10 * sizeof(*ci) + sizeof(*cl));
 
@@ -548,21 +565,22 @@
 	ci = cl->conn_info;
 
 	if (ioctl(dev->sk, HCIGETCONNLIST, (void *) cl) < 0) {
-		g_free(cl);
-		return -EIO;
+		err = -errno;
+		goto failed;
 	}
 
+	err = -ENOENT;
+
 	for (i = 0; i < cl->conn_num; i++, ci++) {
 		if (bacmp(&ci->bdaddr, dba) == 0) {
-			*handle = ci->handle;
-			g_free(cl);
-			return 0;
+			err = disconnect_handle(index, ci->handle, reason);
+			break;
 		}
 	}
 
+failed:
 	g_free(cl);
-
-	return -ENOENT;
+	return err;
 }
 
 static inline int get_bdaddr(int index, uint16_t handle, bdaddr_t *dba)
@@ -625,6 +643,8 @@
 	struct btd_adapter *adapter;
 	struct btd_device *device;
 	struct hci_auth_info_req req;
+	GSList *match;
+	struct link_key_info *key_info;
 	unsigned char key[16];
 	char da[18];
 	uint8_t type;
@@ -652,11 +672,23 @@
 
 	DBG("kernel auth requirements = 0x%02x", req.type);
 
-	if (main_opts.debug_keys && device &&
-					device_get_debug_key(device, key))
+	match = g_slist_find_custom(dev->keys, dba, (GCompareFunc) bacmp);
+	if (match)
+		key_info = match->data;
+	else
+		key_info = NULL;
+
+	DBG("Matching key %s", key_info ? "found" : "not found");
+
+	if (key_info) {
+		memcpy(key, key_info->key, sizeof(key));
+		type = key_info->type;
+	} else
+		type = 0xff;
+
+	if (device && device_get_debug_key(device, key))
 		type = 0x03;
-	else if (read_link_key(&dev->bdaddr, dba, key, &type) < 0 ||
-								type == 0x03) {
+	else if (key_info == NULL || key_info->type == 0x03) {
 		/* Link key not found */
 		hci_send_cmd(dev->sk, OGF_LINK_CTL, OCF_LINK_KEY_NEG_REPLY,
 								6, dba);
@@ -688,44 +720,54 @@
 	struct dev_info *dev = &devs[index];
 	evt_link_key_notify *evt = ptr;
 	bdaddr_t *dba = &evt->bdaddr;
+	struct link_key_info *key_info;
+	GSList *match;
+	uint8_t old_key_type, reason;
 	char da[18];
 	int err;
-	unsigned char old_key[16];
-	uint8_t old_key_type;
 
 	ba2str(dba, da);
 	DBG("hci%d dba %s type %d", index, da, evt->key_type);
 
-	err = read_link_key(&dev->bdaddr, dba, old_key, &old_key_type);
-	if (err < 0)
+	match = g_slist_find_custom(dev->keys, dba, (GCompareFunc) bacmp);
+	if (match)
+		key_info = match->data;
+	else
+		key_info = NULL;
+
+	if (key_info == NULL) {
+		key_info = g_new0(struct link_key_info, 1);
+		bacpy(&key_info->bdaddr, &evt->bdaddr);
 		old_key_type = 0xff;
+	} else {
+		dev->keys = g_slist_remove(dev->keys, key_info);
+		old_key_type = key_info->type;
+	}
+
+	memcpy(key_info->key, evt->link_key, sizeof(evt->link_key));
+	key_info->type = evt->key_type;
+	key_info->pin_len = dev->pin_length;
 
 	err = btd_event_link_key_notify(&dev->bdaddr, dba, evt->link_key,
 					evt->key_type, dev->pin_length,
 					old_key_type);
 	dev->pin_length = -1;
 
-	if (err < 0) {
-		uint16_t handle;
-
-		if (err == -ENODEV)
-			btd_event_bonding_process_complete(&dev->bdaddr, dba,
-							HCI_OE_LOW_RESOURCES);
-		else
-			btd_event_bonding_process_complete(&dev->bdaddr, dba,
-							HCI_MEMORY_FULL);
-
-		if (get_handle(index, dba, &handle) == 0) {
-			disconnect_cp cp;
-
-			memset(&cp, 0, sizeof(cp));
-			cp.handle = htobs(handle);
-			cp.reason = HCI_OE_LOW_RESOURCES;
-
-			hci_send_cmd(dev->sk, OGF_LINK_CTL, OCF_DISCONNECT,
-						DISCONNECT_CP_SIZE, &cp);
-		}
+	if (err == 0) {
+		dev->keys = g_slist_append(dev->keys, key_info);
+		return;
 	}
+
+	g_free(key_info);
+
+	if (err == -ENODEV)
+		reason = HCI_OE_LOW_RESOURCES;
+	else
+		reason = HCI_MEMORY_FULL;
+
+	btd_event_bonding_process_complete(&dev->bdaddr, dba, reason);
+
+	disconnect_addr(index, dba, reason);
 }
 
 static void return_link_keys(int index, void *ptr)
@@ -1842,16 +1884,23 @@
 static void stop_hci_dev(int index)
 {
 	struct dev_info *dev = &devs[index];
-	GIOChannel *chan = dev->io;
 
-	if (!chan)
+	if (dev->sk < 0)
 		return;
 
 	info("Stopping hci%d event socket", index);
 
-	g_source_remove(dev->watch_id);
-	g_io_channel_unref(dev->io);
+	if (dev->watch_id > 0)
+		g_source_remove(dev->watch_id);
+
+	if (dev->io != NULL)
+		g_io_channel_unref(dev->io);
+
 	hci_close_dev(dev->sk);
+
+	g_slist_foreach(dev->keys, (GFunc) g_free, NULL);
+	g_slist_free(dev->keys);
+
 	init_dev_info(index, -1, dev->registered);
 }
 
@@ -2443,12 +2492,8 @@
 
 	DBG("");
 
-	for (i = 0; i <= max_dev; i++) {
-		struct dev_info *dev = &devs[i];
-
-		if (dev->sk >= 0)
-			hci_close_dev(dev->sk);
-	}
+	for (i = 0; i <= max_dev; i++)
+		stop_hci_dev(i);
 
 	g_free(devs);
 	devs = NULL;
@@ -2868,11 +2913,18 @@
 {
 	struct dev_info *dev = &devs[index];
 	delete_stored_link_key_cp cp;
+	GSList *match;
 	char addr[18];
 
 	ba2str(bdaddr, addr);
 	DBG("hci%d dba %s", index, addr);
 
+	match = g_slist_find_custom(dev->keys, bdaddr, (GCompareFunc) bacmp);
+	if (match) {
+		g_free(match->data);
+		dev->keys = g_slist_delete_link(dev->keys, match);
+	}
+
 	memset(&cp, 0, sizeof(cp));
 	bacpy(&cp.bdaddr, bdaddr);
 
@@ -3224,6 +3276,20 @@
 	return 0;
 }
 
+static int hciops_load_keys(int index, GSList *keys)
+{
+	struct dev_info *dev = &devs[index];
+
+	DBG("hci%d keys %d", index, g_slist_length(keys));
+
+	if (dev->keys != NULL)
+		return -EEXIST;
+
+	dev->keys = keys;
+
+	return 0;
+}
+
 static struct btd_adapter_ops hci_ops = {
 	.setup = hciops_setup,
 	.cleanup = hciops_cleanup,
@@ -3265,6 +3331,7 @@
 	.services_updated = hciops_services_updated,
 	.disable_cod_cache = hciops_disable_cod_cache,
 	.restore_powered = hciops_restore_powered,
+	.load_keys = hciops_load_keys,
 };
 
 static int hciops_init(void)
diff --git a/plugins/mgmtops.c b/plugins/mgmtops.c
index 0c8b2fb..3cabf51 100644
--- a/plugins/mgmtops.c
+++ b/plugins/mgmtops.c
@@ -789,6 +789,12 @@
 	return -ENOSYS;
 }
 
+static int mgmt_load_keys(int index, GSList *keys)
+{
+	DBG("index %d keys %d", index, g_slist_length(keys));
+	return -ENOSYS;
+}
+
 static struct btd_adapter_ops mgmt_ops = {
 	.setup = mgmt_setup,
 	.cleanup = mgmt_cleanup,
@@ -830,6 +836,7 @@
 	.services_updated = mgmt_services_updated,
 	.disable_cod_cache = mgmt_disable_cod_cache,
 	.restore_powered = mgmt_restore_powered,
+	.load_keys = mgmt_load_keys,
 };
 
 static int mgmt_init(void)
diff --git a/src/adapter.c b/src/adapter.c
index 2241c4f..6d10a86 100644
--- a/src/adapter.c
+++ b/src/adapter.c
@@ -1857,11 +1857,53 @@
 	g_slist_free(uuids);
 }
 
+struct adapter_keys {
+	struct btd_adapter *adapter;
+	GSList *keys;
+};
+
+static struct link_key_info *get_key_info(const char *addr, const char *value)
+{
+	struct link_key_info *info;
+	char tmp[3];
+	int i;
+
+	if (strlen(value) < 36) {
+		error("Unexpectedly short (%zu) link key line", strlen(value));
+		return NULL;
+	}
+
+	info = g_new0(struct link_key_info, 1);
+
+	str2ba(addr, &info->bdaddr);
+
+	memset(tmp, 0, sizeof(tmp));
+
+	for (i = 0; i < 16; i++) {
+		memcpy(tmp, value + (i * 2), 2);
+		info->key[i] = (uint8_t) strtol(tmp, NULL, 16);
+	}
+
+	memcpy(tmp, value + 33, 2);
+	info->type = (uint8_t) strtol(tmp, NULL, 10);
+
+	memcpy(tmp, value + 35, 2);
+	info->pin_len = strtol(tmp, NULL, 10);
+
+	return info;
+}
+
 static void create_stored_device_from_linkkeys(char *key, char *value,
 							void *user_data)
 {
-	struct btd_adapter *adapter = user_data;
+	struct adapter_keys *keys = user_data;
+	struct btd_adapter *adapter = keys->adapter;
 	struct btd_device *device;
+	struct link_key_info *info;
+
+	info = get_key_info(key, value);
+	if (info)
+		keys->keys = g_slist_append(keys->keys, info);
 
 	if (g_slist_find_custom(adapter->devices, key,
 					(GCompareFunc) device_address_cmp))
@@ -1895,6 +1937,8 @@
 {
 	char filename[PATH_MAX + 1];
 	char srcaddr[18];
+	struct adapter_keys keys = { adapter, NULL };
+	int err;
 
 	ba2str(&adapter->bdaddr, srcaddr);
 
@@ -1903,8 +1947,15 @@
 								adapter);
 
 	create_name(filename, PATH_MAX, STORAGEDIR, srcaddr, "linkkeys");
-	textfile_foreach(filename, create_stored_device_from_linkkeys,
-								adapter);
+	textfile_foreach(filename, create_stored_device_from_linkkeys, &keys);
+
+	err = adapter_ops->load_keys(adapter->dev_id, keys.keys);
+	if (err < 0) {
+		error("Unable to load keys to adapter_ops: %s (%d)",
+							strerror(-err), -err);
+		g_slist_foreach(keys.keys, (GFunc) g_free, NULL);
+		g_slist_free(keys.keys);
+	}
 
 	create_name(filename, PATH_MAX, STORAGEDIR, srcaddr, "blocked");
 	textfile_foreach(filename, create_stored_device_from_blocked, adapter);
diff --git a/src/adapter.h b/src/adapter.h
index 6b8dbc5..a9f1403 100644
--- a/src/adapter.h
+++ b/src/adapter.h
@@ -66,6 +66,13 @@
 
 struct btd_adapter;
 
+struct link_key_info {
+	bdaddr_t bdaddr;
+	unsigned char key[16];
+	uint8_t type;
+	int pin_len;
+};
+
 struct remote_dev_info {
 	bdaddr_t bdaddr;
 	int8_t rssi;
@@ -239,6 +246,7 @@
 	int (*services_updated) (int index);
 	int (*disable_cod_cache) (int index);
 	int (*restore_powered) (int index);
+	int (*load_keys) (int index, GSList *keys);
 };
 
 int btd_register_adapter_ops(struct btd_adapter_ops *ops, gboolean priority);
diff --git a/src/device.c b/src/device.c
index d20a6d4..8d60f4c 100644
--- a/src/device.c
+++ b/src/device.c
@@ -2380,6 +2380,9 @@
 
 gboolean device_get_debug_key(struct btd_device *device, uint8_t *key)
 {
+	if (main_opts.debug_keys == FALSE)
+		return FALSE;
+
 	if (!device->has_debug_key)
 		return FALSE;