From a51afa53f690623333a48b91f390d925896734c2 Mon Sep 17 00:00:00 2001 From: "jabber.developer2" Date: Fri, 29 May 2026 08:49:57 +0300 Subject: [PATCH] fix(editor): guard against re-entrant launch_editor The async editor rewrite (g_child_watch + ui_suspend / ui_resume) lost the editor_task.active check the old pthread implementation had at the top of get_message_from_editor_async. With the guard gone, a second launch_editor call while the first session is in flight would call ui_suspend a second time, fork another editor, and register a second child watch; whichever child exits first runs ui_resume and clears suspension while the other still owns the terminal. Triggering this needs a code path other than the main-loop alt+c / /editor (those can't fire while the main loop is parked in the suspended iteration), so the realistic source is a plugin firing launch_editor from a g_main_context_iteration dispatch. Restore an explicit editor_active static in src/tools/editor.c: check it at the start of launch_editor (bail with a console error), set it just before ui_suspend, and clear it on the fork-error path and at the start of _editor_exit_cb. Keeps the same simple semantics the pre-merge code had, without exposing a new ui.h API. --- src/tools/editor.c | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/tools/editor.c b/src/tools/editor.c index a8648307..dc3bdeb8 100644 --- a/src/tools/editor.c +++ b/src/tools/editor.c @@ -32,9 +32,20 @@ typedef struct EditorContext void* user_data; } EditorContext; +// Set TRUE for the lifetime of an editor session (from launch_editor up to +// _editor_exit_cb / fork-error cleanup). The async editor model lost the old +// editor_task.active check from the pthread implementation; without this +// guard a second launch_editor (e.g. via a plugin dispatched through +// g_main_context_iteration) would call ui_suspend again and fork a second +// editor that fights the first one for the terminal, then prematurely +// ui_resume when either exits. +static gboolean editor_active = FALSE; + static void _editor_exit_cb(GPid pid, gint status, gpointer data) { + editor_active = FALSE; + EditorContext* ctx = data; gchar* contents = NULL; GError* error = NULL; @@ -70,6 +81,11 @@ _editor_exit_cb(GPid pid, gint status, gpointer data) gboolean launch_editor(gchar* initial_content, void (*callback)(gchar* content, void* data), void* user_data) { + if (editor_active) { + cons_show_error("An editor session is already active."); + return TRUE; + } + auto_gchar gchar* filename = NULL; auto_gerror GError* glib_error = NULL; const char* jid = connection_get_barejid(); @@ -118,6 +134,7 @@ launch_editor(gchar* initial_content, void (*callback)(gchar* content, void* dat ctx->callback = callback; ctx->user_data = user_data; + editor_active = TRUE; ui_suspend(); // Force GLib to install its SIGCHLD handler before forking. Creating a @@ -129,6 +146,7 @@ launch_editor(gchar* initial_content, void (*callback)(gchar* content, void* dat pid_t pid = fork(); if (pid == -1) { log_error("[Editor] Failed to fork: %s", strerror(errno)); + editor_active = FALSE; ui_resume(); ui_resize(); cons_show_error("Failed to start editor: %s", strerror(errno));