From b1d91bb34feba305b4be018ccb9b513cbc5b43b6 Mon Sep 17 00:00:00 2001
From: Richard Hughes <richard@hughsie.com>
Date: Mon, 31 Jul 2023 10:43:45 +0100
Subject: [PATCH] Backport a patch to fix a rare multithreaded crash

Resolves: rhbz#2227760
Signed-off-by: Kate Hsuan <hpa@redhat.com>
---
 idle-events-mutex.patch | 126 ++++++++++++++++++++++++++++++++++++++++
 libgusb.spec            |  11 +++-
 2 files changed, 135 insertions(+), 2 deletions(-)
 create mode 100644 idle-events-mutex.patch

diff --git a/idle-events-mutex.patch b/idle-events-mutex.patch
new file mode 100644
index 0000000..9f2a1ab
--- /dev/null
+++ b/idle-events-mutex.patch
@@ -0,0 +1,126 @@
+diff -urNp libgusb-0.3.8.old/gusb/gusb-context.c libgusb-0.3.8/gusb/gusb-context.c
+--- libgusb-0.3.8.old/gusb/gusb-context.c	2023-07-31 10:27:45.903816362 +0100
++++ libgusb-0.3.8/gusb/gusb-context.c	2023-07-31 10:41:22.973605806 +0100
+@@ -54,6 +54,9 @@ struct _GUsbContextPrivate
+ 	GUsbContextFlags		 flags;
+ 	libusb_context			*ctx;
+ 	libusb_hotplug_callback_handle	 hotplug_id;
++	GPtrArray *idle_events;
++	GMutex idle_events_mutex;
++	guint idle_events_id;
+ };
+ 
+ /* not defined in FreeBSD */
+@@ -123,12 +126,18 @@ g_usb_context_dispose (GObject *object)
+ 		g_source_remove (priv->hotplug_poll_id);
+ 		priv->hotplug_poll_id = 0;
+ 	}
++	if (priv->idle_events_id > 0) {
++		g_source_remove(priv->idle_events_id);
++		priv->idle_events_id = 0;
++	}
+ 
+ 	g_clear_pointer (&priv->main_ctx, g_main_context_unref);
+ 	g_clear_pointer (&priv->devices, g_ptr_array_unref);
+ 	g_clear_pointer (&priv->dict_usb_ids, g_hash_table_unref);
+ 	g_clear_pointer (&priv->dict_replug, g_hash_table_unref);
+ 	g_clear_pointer (&priv->ctx, libusb_exit);
++	g_clear_pointer(&priv->idle_events, g_ptr_array_unref);
++	g_mutex_clear(&priv->idle_events_mutex);
+ 
+ 	G_OBJECT_CLASS (g_usb_context_parent_class)->dispose (object);
+ }
+@@ -377,23 +386,48 @@ g_usb_context_idle_helper_free (GUsbCont
+ 	g_free (helper);
+ }
+ 
++static gpointer
++g_usb_context_idle_helper_copy(gconstpointer src, gpointer user_data)
++{
++	GUsbContextIdleHelper *helper_src = (GUsbContextIdleHelper *)src;
++	GUsbContextIdleHelper *helper_dst = g_new0(GUsbContextIdleHelper, 1);
++	helper_dst->context = g_object_ref(helper_src->context);
++	helper_dst->dev = libusb_ref_device(helper_src->dev);
++	helper_dst->event = helper_src->event;
++	return helper_dst;
++}
++
++/* always in the main thread */
+ static gboolean
+ g_usb_context_idle_hotplug_cb (gpointer user_data)
+ {
+-	GUsbContextIdleHelper *helper = (GUsbContextIdleHelper *) user_data;
++	GUsbContext *context = G_USB_CONTEXT(user_data);
++	GUsbContextPrivate *priv = context->priv;
++	g_autoptr(GPtrArray) idle_events = NULL;
+ 
+-	switch (helper->event) {
+-	case LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED:
+-		g_usb_context_add_device (helper->context, helper->dev);
+-		break;
+-	case LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT:
+-		g_usb_context_remove_device (helper->context, helper->dev);
+-		break;
+-	default:
+-		break;
++	/* drain the idle events with the lock held */
++	g_mutex_lock(&priv->idle_events_mutex);
++	idle_events = g_ptr_array_copy(priv->idle_events, g_usb_context_idle_helper_copy, NULL);
++	g_ptr_array_set_size(priv->idle_events, 0);
++	priv->idle_events_id = 0;
++	g_mutex_unlock(&priv->idle_events_mutex);
++
++	/* run the callbacks when not locked */
++	for (guint i = 0; i < idle_events->len; i++) {
++		GUsbContextIdleHelper *helper = g_ptr_array_index(idle_events, i);
++		switch (helper->event) {
++		case LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED:
++			g_usb_context_add_device(helper->context, helper->dev);
++			break;
++		case LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT:
++			g_usb_context_remove_device(helper->context, helper->dev);
++			break;
++		default:
++			break;
++		}
+ 	}
+ 
+-	g_usb_context_idle_helper_free (helper);
++	/* all done */
+ 	return FALSE;
+ }
+ 
+@@ -405,13 +439,19 @@ g_usb_context_hotplug_cb (struct libusb_
+ {
+ 	GUsbContext *context = G_USB_CONTEXT (user_data);
+ 	GUsbContextIdleHelper *helper;
++	GUsbContextPrivate *priv = context->priv;
++	g_autoptr(GMutexLocker) locker = g_mutex_locker_new(&priv->idle_events_mutex);
++
++	g_assert(locker != NULL);
+ 
+ 	helper = g_new0 (GUsbContextIdleHelper, 1);
+-	helper->context = context;
++	helper->context = g_object_ref(context);
+ 	helper->dev = libusb_ref_device (dev);
+ 	helper->event = event;
+ 
+-	g_idle_add (g_usb_context_idle_hotplug_cb, helper);
++	g_ptr_array_add(priv->idle_events, helper);
++	if (priv->idle_events_id == 0)
++		priv->idle_events_id = g_idle_add(g_usb_context_idle_hotplug_cb, context);
+ 
+ 	return 0;
+ }
+@@ -605,6 +645,11 @@ g_usb_context_init (GUsbContext *context
+ 	priv->dict_usb_ids = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
+ 	priv->dict_replug = g_hash_table_new_full (g_str_hash, g_str_equal,
+ 						   g_free, NULL);
++
++	/* to escape the thread into the mainloop */
++	g_mutex_init(&priv->idle_events_mutex);
++	priv->idle_events =
++	    g_ptr_array_new_with_free_func((GDestroyNotify)g_usb_context_idle_helper_free);
+ }
+ 
+ static gboolean
diff --git a/libgusb.spec b/libgusb.spec
index 4d74d18..259b7d0 100644
--- a/libgusb.spec
+++ b/libgusb.spec
@@ -1,11 +1,14 @@
 Summary:   GLib wrapper around libusb1
 Name:      libgusb
 Version:   0.3.8
-Release:   1%{?dist}
+Release:   2%{?dist}
 License:   LGPLv2+
 URL:       https://github.com/hughsie/libgusb
 Source0:   http://people.freedesktop.org/~hughsient/releases/%{name}-%{version}.tar.xz
 
+# backport from upstream
+Patch0:    idle-events-mutex.patch
+
 BuildRequires: glib2-devel >= 2.38.0
 BuildRequires: gobject-introspection-devel
 BuildRequires: gtk-doc
@@ -26,7 +29,7 @@ Requires: %{name} = %{version}-%{release}
 GLib headers and libraries for gusb.
 
 %prep
-%setup -q
+%autosetup -p1
 
 %build
 %meson -Dvapi=true -Dtests=true
@@ -55,6 +58,10 @@ GLib headers and libraries for gusb.
 %{_datadir}/vala/vapi/gusb.vapi
 
 %changelog
+* Mon Jul 31 2023 Richard Hughes <richard@hughsie.com> 0.3.8-2
+- Backport a patch to fix a rare multithreaded crash on device replug
+- Resolves: rhbz#2227760
+
 * Wed Oct 06 2021 Richard Hughes <richard@hughsie.com> 0.3.8-1
 - New upstream version
 - Add new API requested by fwupd