Skip to content

Commit 5f11482

Browse files
committed
Provide a variant of simple_prompt() that can be interrupted by ^C.
Up to now, you couldn't escape out of psql's \password command by typing control-C (or other local spelling of SIGINT). This is pretty user-unfriendly, so improve it. To do so, we have to modify the functions provided by pg_get_line.c; but we don't want to mess with psql's SIGINT handler setup, so provide an API that lets that handler cause the cancel to occur. This relies on the assumption that we won't do any major harm by longjmp'ing out of fgets(). While that's obviously a little shaky, we've long had the same assumption in the main input loop, and few issues have been reported. psql has some other simple_prompt() calls that could usefully be improved the same way; for now, just deal with \password. Nathan Bossart, minor tweaks by me Discussion: https://postgr.es/m/747443.1635536754@sss.pgh.pa.us
1 parent a148f8b commit 5f11482

File tree

7 files changed

+100
-19
lines changed

7 files changed

+100
-19
lines changed

src/backend/libpq/hba.c

+1-1
Original file line numberDiff line numberDiff line change
@@ -500,7 +500,7 @@ tokenize_file(const char *filename, FILE *file, List **tok_lines, int elevel)
500500
/* Collect the next input line, handling backslash continuations */
501501
resetStringInfo(&buf);
502502

503-
while (pg_get_line_append(file, &buf))
503+
while (pg_get_line_append(file, &buf, NULL))
504504
{
505505
/* Strip trailing newline, including \r in case we're on Windows */
506506
buf.len = pg_strip_crlf(buf.data);

src/bin/initdb/initdb.c

+1-1
Original file line numberDiff line numberDiff line change
@@ -1497,7 +1497,7 @@ get_su_pwd(void)
14971497
pwfilename);
14981498
exit(1);
14991499
}
1500-
pwd1 = pg_get_line(pwf);
1500+
pwd1 = pg_get_line(pwf, NULL);
15011501
if (!pwd1)
15021502
{
15031503
if (ferror(pwf))

src/bin/psql/command.c

+21-7
Original file line numberDiff line numberDiff line change
@@ -2025,9 +2025,10 @@ exec_command_password(PsqlScanState scan_state, bool active_branch)
20252025
{
20262026
char *user = psql_scan_slash_option(scan_state,
20272027
OT_SQLID, NULL, true);
2028-
char *pw1;
2029-
char *pw2;
2028+
char *pw1 = NULL;
2029+
char *pw2 = NULL;
20302030
PQExpBufferData buf;
2031+
PromptInterruptContext prompt_ctx;
20312032

20322033
if (user == NULL)
20332034
{
@@ -2042,13 +2043,24 @@ exec_command_password(PsqlScanState scan_state, bool active_branch)
20422043
PQclear(res);
20432044
}
20442045

2046+
/* Set up to let SIGINT cancel simple_prompt_extended() */
2047+
prompt_ctx.jmpbuf = sigint_interrupt_jmp;
2048+
prompt_ctx.enabled = &sigint_interrupt_enabled;
2049+
prompt_ctx.canceled = false;
2050+
20452051
initPQExpBuffer(&buf);
20462052
printfPQExpBuffer(&buf, _("Enter new password for user \"%s\": "), user);
20472053

2048-
pw1 = simple_prompt(buf.data, false);
2049-
pw2 = simple_prompt("Enter it again: ", false);
2054+
pw1 = simple_prompt_extended(buf.data, false, &prompt_ctx);
2055+
if (!prompt_ctx.canceled)
2056+
pw2 = simple_prompt_extended("Enter it again: ", false, &prompt_ctx);
20502057

2051-
if (strcmp(pw1, pw2) != 0)
2058+
if (prompt_ctx.canceled)
2059+
{
2060+
/* fail silently */
2061+
success = false;
2062+
}
2063+
else if (strcmp(pw1, pw2) != 0)
20522064
{
20532065
pg_log_error("Passwords didn't match.");
20542066
success = false;
@@ -2081,8 +2093,10 @@ exec_command_password(PsqlScanState scan_state, bool active_branch)
20812093
}
20822094

20832095
free(user);
2084-
free(pw1);
2085-
free(pw2);
2096+
if (pw1)
2097+
free(pw1);
2098+
if (pw2)
2099+
free(pw2);
20862100
termPQExpBuffer(&buf);
20872101
}
20882102
else

src/bin/psql/nls.mk

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,5 @@ GETTEXT_FILES = $(FRONTEND_COMMON_GETTEXT_FILES) \
1010
../../common/exec.c ../../common/fe_memutils.c ../../common/username.c \
1111
../../common/wait_error.c
1212
GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
13-
N_ simple_prompt
13+
N_ simple_prompt simple_prompt_extended
1414
GETTEXT_FLAGS = $(FRONTEND_COMMON_GETTEXT_FLAGS)

src/common/pg_get_line.c

+46-6
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
#include "postgres_fe.h"
1919
#endif
2020

21+
#include <setjmp.h>
22+
2123
#include "common/string.h"
2224
#include "lib/stringinfo.h"
2325

@@ -47,15 +49,20 @@
4749
* to collect lots of long-lived data. A less memory-hungry option
4850
* is to use pg_get_line_buf() or pg_get_line_append() in a loop,
4951
* then pstrdup() each line.
52+
*
53+
* prompt_ctx can optionally be provided to allow this function to be
54+
* canceled via an existing SIGINT signal handler that will longjmp to the
55+
* specified place only when *(prompt_ctx->enabled) is true. If canceled,
56+
* this function returns NULL, and prompt_ctx->canceled is set to true.
5057
*/
5158
char *
52-
pg_get_line(FILE *stream)
59+
pg_get_line(FILE *stream, PromptInterruptContext *prompt_ctx)
5360
{
5461
StringInfoData buf;
5562

5663
initStringInfo(&buf);
5764

58-
if (!pg_get_line_append(stream, &buf))
65+
if (!pg_get_line_append(stream, &buf, prompt_ctx))
5966
{
6067
/* ensure that free() doesn't mess up errno */
6168
int save_errno = errno;
@@ -89,7 +96,7 @@ pg_get_line_buf(FILE *stream, StringInfo buf)
8996
{
9097
/* We just need to drop any data from the previous call */
9198
resetStringInfo(buf);
92-
return pg_get_line_append(stream, buf);
99+
return pg_get_line_append(stream, buf, NULL);
93100
}
94101

95102
/*
@@ -107,15 +114,48 @@ pg_get_line_buf(FILE *stream, StringInfo buf)
107114
*
108115
* In the false-result case, the contents of *buf are logically unmodified,
109116
* though it's possible that the buffer has been resized.
117+
*
118+
* prompt_ctx can optionally be provided to allow this function to be
119+
* canceled via an existing SIGINT signal handler that will longjmp to the
120+
* specified place only when *(prompt_ctx->enabled) is true. If canceled,
121+
* this function returns false, and prompt_ctx->canceled is set to true.
110122
*/
111123
bool
112-
pg_get_line_append(FILE *stream, StringInfo buf)
124+
pg_get_line_append(FILE *stream, StringInfo buf,
125+
PromptInterruptContext *prompt_ctx)
113126
{
114127
int orig_len = buf->len;
115128

116-
/* Read some data, appending it to whatever we already have */
117-
while (fgets(buf->data + buf->len, buf->maxlen - buf->len, stream) != NULL)
129+
if (prompt_ctx && sigsetjmp(*((sigjmp_buf *) prompt_ctx->jmpbuf), 1) != 0)
118130
{
131+
/* Got here with longjmp */
132+
prompt_ctx->canceled = true;
133+
/* Discard any data we collected before detecting error */
134+
buf->len = orig_len;
135+
buf->data[orig_len] = '\0';
136+
return false;
137+
}
138+
139+
/* Loop until newline or EOF/error */
140+
for (;;)
141+
{
142+
char *res;
143+
144+
/* Enable longjmp while waiting for input */
145+
if (prompt_ctx)
146+
*(prompt_ctx->enabled) = true;
147+
148+
/* Read some data, appending it to whatever we already have */
149+
res = fgets(buf->data + buf->len, buf->maxlen - buf->len, stream);
150+
151+
/* Disable longjmp again, then break if fgets failed */
152+
if (prompt_ctx)
153+
*(prompt_ctx->enabled) = false;
154+
155+
if (res == NULL)
156+
break;
157+
158+
/* Got data, so update buf->len */
119159
buf->len += strlen(buf->data + buf->len);
120160

121161
/* Done if we have collected a newline */

src/common/sprompt.c

+17-1
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,22 @@
3636
*/
3737
char *
3838
simple_prompt(const char *prompt, bool echo)
39+
{
40+
return simple_prompt_extended(prompt, echo, NULL);
41+
}
42+
43+
/*
44+
* simple_prompt_extended
45+
*
46+
* This is the same as simple_prompt(), except that prompt_ctx can
47+
* optionally be provided to allow this function to be canceled via an
48+
* existing SIGINT signal handler that will longjmp to the specified place
49+
* only when *(prompt_ctx->enabled) is true. If canceled, this function
50+
* returns an empty string, and prompt_ctx->canceled is set to true.
51+
*/
52+
char *
53+
simple_prompt_extended(const char *prompt, bool echo,
54+
PromptInterruptContext *prompt_ctx)
3955
{
4056
char *result;
4157
FILE *termin,
@@ -126,7 +142,7 @@ simple_prompt(const char *prompt, bool echo)
126142
fflush(termout);
127143
}
128144

129-
result = pg_get_line(termin);
145+
result = pg_get_line(termin, prompt_ctx);
130146

131147
/* If we failed to read anything, just return an empty string */
132148
if (result == NULL)

src/include/common/string.h

+13-2
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,14 @@
1212

1313
struct StringInfoData; /* avoid including stringinfo.h here */
1414

15+
typedef struct PromptInterruptContext
16+
{
17+
/* To avoid including <setjmp.h> here, jmpbuf is declared "void *" */
18+
void *jmpbuf; /* existing longjmp buffer */
19+
volatile bool *enabled; /* flag that enables longjmp-on-interrupt */
20+
bool canceled; /* indicates whether cancellation occurred */
21+
} PromptInterruptContext;
22+
1523
/* functions in src/common/string.c */
1624
extern bool pg_str_endswith(const char *str, const char *end);
1725
extern int strtoint(const char *pg_restrict str, char **pg_restrict endptr,
@@ -21,11 +29,14 @@ extern int pg_strip_crlf(char *str);
2129
extern bool pg_is_ascii(const char *str);
2230

2331
/* functions in src/common/pg_get_line.c */
24-
extern char *pg_get_line(FILE *stream);
32+
extern char *pg_get_line(FILE *stream, PromptInterruptContext *prompt_ctx);
2533
extern bool pg_get_line_buf(FILE *stream, struct StringInfoData *buf);
26-
extern bool pg_get_line_append(FILE *stream, struct StringInfoData *buf);
34+
extern bool pg_get_line_append(FILE *stream, struct StringInfoData *buf,
35+
PromptInterruptContext *prompt_ctx);
2736

2837
/* functions in src/common/sprompt.c */
2938
extern char *simple_prompt(const char *prompt, bool echo);
39+
extern char *simple_prompt_extended(const char *prompt, bool echo,
40+
PromptInterruptContext *prompt_ctx);
3041

3142
#endif /* COMMON_STRING_H */

0 commit comments

Comments
 (0)