From 4023b98a0cef1b45b18fa2257e82d9ae1543c9ca Mon Sep 17 00:00:00 2001
From: Jelte Fennema-Nio <postgres@jeltef.nl>
Date: Wed, 4 Nov 2026 21:07:26 +0100
Subject: [PATCH v13 1/5] psql: Stop scripts on Ctrl+C while waiting for COPY
 or prompt input

When running a script non-interactively, pressing Ctrl+C while a query
is running aborts the whole script and psql exits with code 3. But when
psql was instead waiting for input, e.g. COPY FROM STDIN data via
\copy ... from pstdin, or input for \prompt or \password, pressing
Ctrl+C only aborted that single command and the rest of the script
continued running. For example with this script:

    \copy t1 from pstdin
    select now();

Pressing Ctrl+C while psql is waiting for COPY data would abort the
COPY, but then still run the SELECT and exit successfully.

The reason for this inconsistency was that psql_cancel_callback() only
set cancel_pressed when it did not longjmp out of waiting for input.
That's fine for the longjmp target in MainLoop(), which itself already
stops a non-interactive script when psql gets interrupted while waiting
for the next line of input. But the longjmp targets in handleCopyIn()
and pg_get_line_append() (used by simple_prompt_extended()) simply
return to their callers, which then have no way of knowing that the
user wanted to stop the script.

This fixes that by always setting cancel_pressed before doing the
longjmp. Interactive sessions are not affected, because MainLoop()
clears cancel_pressed again for those.
---
 src/bin/psql/common.c | 10 +++++++---
 1 file changed, 7 insertions(+), 3 deletions(-)

diff --git a/src/bin/psql/common.c b/src/bin/psql/common.c
index 740c1fcadb9..0ba1cbb460c 100644
--- a/src/bin/psql/common.c
+++ b/src/bin/psql/common.c
@@ -311,6 +311,13 @@ sigjmp_buf	sigint_interrupt_jmp;
 static void
 psql_cancel_callback(void)
 {
+	/*
+	 * Set this before the longjmp below, so that a non-interactive script
+	 * also stops when Ctrl+C is pressed while psql is waiting for COPY data
+	 * or \prompt input, just like it does when a query is running.
+	 */
+	cancel_pressed = true;
+
 #ifndef WIN32
 	/* if we are waiting for input, longjmp out of it */
 	if (sigint_interrupt_enabled)
@@ -319,9 +326,6 @@ psql_cancel_callback(void)
 		siglongjmp(sigint_interrupt_jmp, 1);
 	}
 #endif
-
-	/* else, set cancel flag to stop any long-running loops */
-	cancel_pressed = true;
 }
 
 void
-- 
2.55.0

