Blob Blame History Raw
From 9f8fa823a2c8fec78e5a210f5c4a77114652d2ea Mon Sep 17 00:00:00 2001
From: Bastien Nocera <hadess@hadess.net>
Date: Fri, 1 Sep 2017 17:23:38 +0200
Subject: [PATCH 9/9] plugins/sixaxis: Cancel cable pairing if unplugged

Cancel pending authorization requests when the device is unplugged
while a request is pending.
---
 plugins/sixaxis.c | 138 ++++++++++++++++++++++++++++++++++++++++++++++++------
 1 file changed, 123 insertions(+), 15 deletions(-)

diff --git a/plugins/sixaxis.c b/plugins/sixaxis.c
index eb15acb92..5cae52c68 100644
--- a/plugins/sixaxis.c
+++ b/plugins/sixaxis.c
@@ -51,6 +51,8 @@
 #include "profiles/input/sixaxis.h"
 
 struct authentication_closure {
+	guint auth_id;
+	char *sysfs_path;
 	struct btd_adapter *adapter;
 	struct btd_device *device;
 	int fd;
@@ -58,9 +60,29 @@ struct authentication_closure {
 	CablePairingType type;
 };
 
+struct authentication_destroy_closure {
+	struct authentication_closure *closure;
+	bool remove_device;
+};
+
 static struct udev *ctx = NULL;
 static struct udev_monitor *monitor = NULL;
 static guint watch_id = 0;
+static GHashTable *pending_auths = NULL; /* key = sysfs_path (const str), value = auth_closure */
+
+/* Make sure to unset auth_id if already handled */
+static void auth_closure_destroy(struct authentication_closure *closure,
+				bool remove_device)
+{
+	if (closure->auth_id)
+		btd_cancel_authorization(closure->auth_id);
+
+	if (remove_device)
+		btd_adapter_remove_device(closure->adapter, closure->device);
+	close(closure->fd);
+	g_free(closure->sysfs_path);
+	g_free(closure);
+}
 
 static int sixaxis_get_device_bdaddr(int fd, bdaddr_t *bdaddr)
 {
@@ -214,41 +236,83 @@ static int set_master_bdaddr(int fd, const bdaddr_t *bdaddr,
 	return -1;
 }
 
+static bool is_auth_pending(struct authentication_closure *closure)
+{
+	GHashTableIter iter;
+	gpointer value;
+
+	g_hash_table_iter_init(&iter, pending_auths);
+	while (g_hash_table_iter_next(&iter, NULL, &value)) {
+		struct authentication_closure *c = value;
+		if (c == closure)
+			return true;
+	}
+	return false;
+}
+
+static gboolean auth_closure_destroy_idle(gpointer user_data)
+{
+	struct authentication_destroy_closure *destroy = user_data;
+
+	auth_closure_destroy(destroy->closure, destroy->remove_device);
+	g_free(destroy);
+
+	return false;
+}
+
 static void agent_auth_cb(DBusError *derr,
 				void *user_data)
 {
 	struct authentication_closure *closure = user_data;
+	struct authentication_destroy_closure *destroy;
 	char master_addr[18], adapter_addr[18];
 	bdaddr_t master_bdaddr;
 	const bdaddr_t *adapter_bdaddr;
+	bool remove_device = true;
+
+	if (!is_auth_pending(closure))
+		return;
+
+	/* Don't try to remove this auth, we're handling it already */
+	closure->auth_id = 0;
 
 	if (derr != NULL) {
 		DBG("Agent replied negatively, removing temporary device");
-		goto error;
+		goto out;
 	}
 
-	btd_device_set_temporary(closure->device, false);
-
 	if (get_master_bdaddr(closure->fd, &master_bdaddr, closure->type) < 0)
-		goto error;
+		goto out;
 
 	adapter_bdaddr = btd_adapter_get_address(closure->adapter);
 	if (bacmp(adapter_bdaddr, &master_bdaddr)) {
 		if (set_master_bdaddr(closure->fd, adapter_bdaddr, closure->type) < 0)
-			goto error;
+			goto out;
 	}
 
+	remove_device = false;
+	btd_device_set_temporary(closure->device, false);
+
 	ba2str(&master_bdaddr, master_addr);
 	ba2str(adapter_bdaddr, adapter_addr);
 	DBG("remote %s old_master %s new_master %s",
 				closure->device_addr, master_addr, adapter_addr);
 
-error:
-	close(closure->fd);
-	g_free(closure);
+out:
+	g_hash_table_steal(pending_auths, closure->sysfs_path);
+
+	/* btd_adapter_remove_device() cannot be called in this
+	 * callback or it would lead to a double-free in while
+	 * trying to cancel the authentication that's being processed,
+	 * so clean up in an idle */
+	destroy = g_new0(struct authentication_destroy_closure, 1);
+	destroy->closure = closure;
+	destroy->remove_device = remove_device;
+	g_idle_add(auth_closure_destroy_idle, destroy);
 }
 
 static bool setup_device(int fd,
+				const char *sysfs_path,
 				const char *name,
 				uint16_t source,
 				uint16_t vid,
@@ -296,12 +360,15 @@ static bool setup_device(int fd,
 	}
 	closure->adapter = adapter;
 	closure->device = device;
+	closure->sysfs_path = g_strdup(sysfs_path);
 	closure->fd = fd;
 	closure->type = type;
 	memcpy(&closure->device_addr, device_addr, sizeof(device_addr));
 	adapter_bdaddr = btd_adapter_get_address(adapter);
-	btd_request_authorization_cable_configured(adapter_bdaddr, &device_bdaddr,
-				HID_UUID, agent_auth_cb, closure);
+	closure->auth_id = btd_request_authorization_cable_configured(adapter_bdaddr, &device_bdaddr,
+								HID_UUID, agent_auth_cb, closure);
+
+	g_hash_table_insert(pending_auths, closure->sysfs_path, closure);
 
 	return true;
 }
@@ -310,12 +377,14 @@ static CablePairingType get_pairing_type_for_device(struct udev_device *udevice,
 								uint16_t  *bus,
 								uint16_t  *vid,
 								uint16_t  *pid,
+								char     **sysfs_path,
 								char     **name,
 								uint16_t  *source,
 								uint16_t  *version)
 {
 	struct udev_device *hid_parent;
 	const char *hid_id;
+	CablePairingType ret;
 
 	hid_parent = udev_device_get_parent_with_subsystem_devtype(udevice,
 								"hid", NULL);
@@ -327,14 +396,17 @@ static CablePairingType get_pairing_type_for_device(struct udev_device *udevice,
 	if (sscanf(hid_id, "%hx:%hx:%hx", bus, vid, pid) != 3)
 		return -1;
 
-	return get_pairing_type(*vid, *pid, name, source, version);
+	ret = get_pairing_type(*vid, *pid, name, source, version);
+	*sysfs_path = g_strdup(udev_device_get_syspath(udevice));
+
+	return ret;
 }
 
 static void device_added(struct udev_device *udevice)
 {
 	struct btd_adapter *adapter;
 	uint16_t bus, vid, pid, source, version;
-	char *name = NULL;
+	char *name = NULL, *sysfs_path = NULL;
 	CablePairingType type;
 	int fd;
 
@@ -346,6 +418,7 @@ static void device_added(struct udev_device *udevice)
 						&bus,
 						&vid,
 						&pid,
+						&sysfs_path,
 						&name,
 						&source,
 						&version);
@@ -355,20 +428,39 @@ static void device_added(struct udev_device *udevice)
 	if (bus != BUS_USB)
 		return;
 
-	info("sixaxis: compatible device connected: %s (%04X:%04X)",
-				name, vid, pid);
+	info("sixaxis: compatible device connected: %s (%04X:%04X %s)",
+				name, vid, pid, sysfs_path);
 
 	fd = open(udev_device_get_devnode(udevice), O_RDWR);
 	if (fd < 0) {
 		g_free(name);
+		g_free(sysfs_path);
 		return;
 	}
 
 	/* Only close the fd if an authentication is not pending */
-	if (!setup_device(fd, name, source, vid, pid, version, type, adapter))
+	if (!setup_device(fd, sysfs_path, name, source, vid, pid, version, type, adapter))
 		close(fd);
 
 	g_free(name);
+	g_free(sysfs_path);
+}
+
+static void device_removed(struct udev_device *udevice)
+{
+	struct authentication_closure *closure;
+	const char *sysfs_path;
+
+	sysfs_path = udev_device_get_syspath(udevice);
+	if (!sysfs_path)
+		return;
+
+	closure = g_hash_table_lookup(pending_auths, sysfs_path);
+	if (!closure)
+		return;
+
+	g_hash_table_steal(pending_auths, sysfs_path);
+	auth_closure_destroy(closure, true);
 }
 
 static gboolean monitor_watch(GIOChannel *source, GIOCondition condition,
@@ -382,6 +474,8 @@ static gboolean monitor_watch(GIOChannel *source, GIOCondition condition,
 
 	if (!g_strcmp0(udev_device_get_action(udevice), "add"))
 		device_added(udevice);
+	else if (!g_strcmp0(udev_device_get_action(udevice), "remove"))
+		device_removed(udevice);
 
 	udev_device_unref(udevice);
 
@@ -415,13 +509,27 @@ static int sixaxis_init(void)
 	watch_id = g_io_add_watch(channel, G_IO_IN, monitor_watch, NULL);
 	g_io_channel_unref(channel);
 
+	pending_auths = g_hash_table_new(g_str_hash,
+					g_str_equal);
+
 	return 0;
 }
 
 static void sixaxis_exit(void)
 {
+	GHashTableIter iter;
+	gpointer value;
+
 	DBG("");
 
+	g_hash_table_iter_init(&iter, pending_auths);
+	while (g_hash_table_iter_next(&iter, NULL, &value)) {
+		struct authentication_closure *closure = value;
+		auth_closure_destroy(closure, true);
+	}
+	g_hash_table_destroy(pending_auths);
+	pending_auths = NULL;
+
 	g_source_remove(watch_id);
 	watch_id = 0;
 
-- 
2.14.1