mirror of
https://git.postgresql.org/git/postgresql.git
synced 2024-12-27 08:39:28 +08:00
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
This commit is contained in:
parent
a148f8bc04
commit
5f1148224b
@ -500,7 +500,7 @@ tokenize_file(const char *filename, FILE *file, List **tok_lines, int elevel)
|
|||||||
/* Collect the next input line, handling backslash continuations */
|
/* Collect the next input line, handling backslash continuations */
|
||||||
resetStringInfo(&buf);
|
resetStringInfo(&buf);
|
||||||
|
|
||||||
while (pg_get_line_append(file, &buf))
|
while (pg_get_line_append(file, &buf, NULL))
|
||||||
{
|
{
|
||||||
/* Strip trailing newline, including \r in case we're on Windows */
|
/* Strip trailing newline, including \r in case we're on Windows */
|
||||||
buf.len = pg_strip_crlf(buf.data);
|
buf.len = pg_strip_crlf(buf.data);
|
||||||
|
@ -1497,7 +1497,7 @@ get_su_pwd(void)
|
|||||||
pwfilename);
|
pwfilename);
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
pwd1 = pg_get_line(pwf);
|
pwd1 = pg_get_line(pwf, NULL);
|
||||||
if (!pwd1)
|
if (!pwd1)
|
||||||
{
|
{
|
||||||
if (ferror(pwf))
|
if (ferror(pwf))
|
||||||
|
@ -2025,9 +2025,10 @@ exec_command_password(PsqlScanState scan_state, bool active_branch)
|
|||||||
{
|
{
|
||||||
char *user = psql_scan_slash_option(scan_state,
|
char *user = psql_scan_slash_option(scan_state,
|
||||||
OT_SQLID, NULL, true);
|
OT_SQLID, NULL, true);
|
||||||
char *pw1;
|
char *pw1 = NULL;
|
||||||
char *pw2;
|
char *pw2 = NULL;
|
||||||
PQExpBufferData buf;
|
PQExpBufferData buf;
|
||||||
|
PromptInterruptContext prompt_ctx;
|
||||||
|
|
||||||
if (user == NULL)
|
if (user == NULL)
|
||||||
{
|
{
|
||||||
@ -2042,13 +2043,24 @@ exec_command_password(PsqlScanState scan_state, bool active_branch)
|
|||||||
PQclear(res);
|
PQclear(res);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Set up to let SIGINT cancel simple_prompt_extended() */
|
||||||
|
prompt_ctx.jmpbuf = sigint_interrupt_jmp;
|
||||||
|
prompt_ctx.enabled = &sigint_interrupt_enabled;
|
||||||
|
prompt_ctx.canceled = false;
|
||||||
|
|
||||||
initPQExpBuffer(&buf);
|
initPQExpBuffer(&buf);
|
||||||
printfPQExpBuffer(&buf, _("Enter new password for user \"%s\": "), user);
|
printfPQExpBuffer(&buf, _("Enter new password for user \"%s\": "), user);
|
||||||
|
|
||||||
pw1 = simple_prompt(buf.data, false);
|
pw1 = simple_prompt_extended(buf.data, false, &prompt_ctx);
|
||||||
pw2 = simple_prompt("Enter it again: ", false);
|
if (!prompt_ctx.canceled)
|
||||||
|
pw2 = simple_prompt_extended("Enter it again: ", false, &prompt_ctx);
|
||||||
|
|
||||||
if (strcmp(pw1, pw2) != 0)
|
if (prompt_ctx.canceled)
|
||||||
|
{
|
||||||
|
/* fail silently */
|
||||||
|
success = false;
|
||||||
|
}
|
||||||
|
else if (strcmp(pw1, pw2) != 0)
|
||||||
{
|
{
|
||||||
pg_log_error("Passwords didn't match.");
|
pg_log_error("Passwords didn't match.");
|
||||||
success = false;
|
success = false;
|
||||||
@ -2081,8 +2093,10 @@ exec_command_password(PsqlScanState scan_state, bool active_branch)
|
|||||||
}
|
}
|
||||||
|
|
||||||
free(user);
|
free(user);
|
||||||
free(pw1);
|
if (pw1)
|
||||||
free(pw2);
|
free(pw1);
|
||||||
|
if (pw2)
|
||||||
|
free(pw2);
|
||||||
termPQExpBuffer(&buf);
|
termPQExpBuffer(&buf);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -10,5 +10,5 @@ GETTEXT_FILES = $(FRONTEND_COMMON_GETTEXT_FILES) \
|
|||||||
../../common/exec.c ../../common/fe_memutils.c ../../common/username.c \
|
../../common/exec.c ../../common/fe_memutils.c ../../common/username.c \
|
||||||
../../common/wait_error.c
|
../../common/wait_error.c
|
||||||
GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
|
GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
|
||||||
N_ simple_prompt
|
N_ simple_prompt simple_prompt_extended
|
||||||
GETTEXT_FLAGS = $(FRONTEND_COMMON_GETTEXT_FLAGS)
|
GETTEXT_FLAGS = $(FRONTEND_COMMON_GETTEXT_FLAGS)
|
||||||
|
@ -18,6 +18,8 @@
|
|||||||
#include "postgres_fe.h"
|
#include "postgres_fe.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include <setjmp.h>
|
||||||
|
|
||||||
#include "common/string.h"
|
#include "common/string.h"
|
||||||
#include "lib/stringinfo.h"
|
#include "lib/stringinfo.h"
|
||||||
|
|
||||||
@ -47,15 +49,20 @@
|
|||||||
* to collect lots of long-lived data. A less memory-hungry option
|
* to collect lots of long-lived data. A less memory-hungry option
|
||||||
* is to use pg_get_line_buf() or pg_get_line_append() in a loop,
|
* is to use pg_get_line_buf() or pg_get_line_append() in a loop,
|
||||||
* then pstrdup() each line.
|
* then pstrdup() each line.
|
||||||
|
*
|
||||||
|
* prompt_ctx can optionally be provided to allow this function to be
|
||||||
|
* canceled via an existing SIGINT signal handler that will longjmp to the
|
||||||
|
* specified place only when *(prompt_ctx->enabled) is true. If canceled,
|
||||||
|
* this function returns NULL, and prompt_ctx->canceled is set to true.
|
||||||
*/
|
*/
|
||||||
char *
|
char *
|
||||||
pg_get_line(FILE *stream)
|
pg_get_line(FILE *stream, PromptInterruptContext *prompt_ctx)
|
||||||
{
|
{
|
||||||
StringInfoData buf;
|
StringInfoData buf;
|
||||||
|
|
||||||
initStringInfo(&buf);
|
initStringInfo(&buf);
|
||||||
|
|
||||||
if (!pg_get_line_append(stream, &buf))
|
if (!pg_get_line_append(stream, &buf, prompt_ctx))
|
||||||
{
|
{
|
||||||
/* ensure that free() doesn't mess up errno */
|
/* ensure that free() doesn't mess up errno */
|
||||||
int save_errno = errno;
|
int save_errno = errno;
|
||||||
@ -89,7 +96,7 @@ pg_get_line_buf(FILE *stream, StringInfo buf)
|
|||||||
{
|
{
|
||||||
/* We just need to drop any data from the previous call */
|
/* We just need to drop any data from the previous call */
|
||||||
resetStringInfo(buf);
|
resetStringInfo(buf);
|
||||||
return pg_get_line_append(stream, buf);
|
return pg_get_line_append(stream, buf, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -107,15 +114,48 @@ pg_get_line_buf(FILE *stream, StringInfo buf)
|
|||||||
*
|
*
|
||||||
* In the false-result case, the contents of *buf are logically unmodified,
|
* In the false-result case, the contents of *buf are logically unmodified,
|
||||||
* though it's possible that the buffer has been resized.
|
* though it's possible that the buffer has been resized.
|
||||||
|
*
|
||||||
|
* prompt_ctx can optionally be provided to allow this function to be
|
||||||
|
* canceled via an existing SIGINT signal handler that will longjmp to the
|
||||||
|
* specified place only when *(prompt_ctx->enabled) is true. If canceled,
|
||||||
|
* this function returns false, and prompt_ctx->canceled is set to true.
|
||||||
*/
|
*/
|
||||||
bool
|
bool
|
||||||
pg_get_line_append(FILE *stream, StringInfo buf)
|
pg_get_line_append(FILE *stream, StringInfo buf,
|
||||||
|
PromptInterruptContext *prompt_ctx)
|
||||||
{
|
{
|
||||||
int orig_len = buf->len;
|
int orig_len = buf->len;
|
||||||
|
|
||||||
/* Read some data, appending it to whatever we already have */
|
if (prompt_ctx && sigsetjmp(*((sigjmp_buf *) prompt_ctx->jmpbuf), 1) != 0)
|
||||||
while (fgets(buf->data + buf->len, buf->maxlen - buf->len, stream) != NULL)
|
|
||||||
{
|
{
|
||||||
|
/* Got here with longjmp */
|
||||||
|
prompt_ctx->canceled = true;
|
||||||
|
/* Discard any data we collected before detecting error */
|
||||||
|
buf->len = orig_len;
|
||||||
|
buf->data[orig_len] = '\0';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Loop until newline or EOF/error */
|
||||||
|
for (;;)
|
||||||
|
{
|
||||||
|
char *res;
|
||||||
|
|
||||||
|
/* Enable longjmp while waiting for input */
|
||||||
|
if (prompt_ctx)
|
||||||
|
*(prompt_ctx->enabled) = true;
|
||||||
|
|
||||||
|
/* Read some data, appending it to whatever we already have */
|
||||||
|
res = fgets(buf->data + buf->len, buf->maxlen - buf->len, stream);
|
||||||
|
|
||||||
|
/* Disable longjmp again, then break if fgets failed */
|
||||||
|
if (prompt_ctx)
|
||||||
|
*(prompt_ctx->enabled) = false;
|
||||||
|
|
||||||
|
if (res == NULL)
|
||||||
|
break;
|
||||||
|
|
||||||
|
/* Got data, so update buf->len */
|
||||||
buf->len += strlen(buf->data + buf->len);
|
buf->len += strlen(buf->data + buf->len);
|
||||||
|
|
||||||
/* Done if we have collected a newline */
|
/* Done if we have collected a newline */
|
||||||
|
@ -36,6 +36,22 @@
|
|||||||
*/
|
*/
|
||||||
char *
|
char *
|
||||||
simple_prompt(const char *prompt, bool echo)
|
simple_prompt(const char *prompt, bool echo)
|
||||||
|
{
|
||||||
|
return simple_prompt_extended(prompt, echo, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* simple_prompt_extended
|
||||||
|
*
|
||||||
|
* This is the same as simple_prompt(), except that prompt_ctx can
|
||||||
|
* optionally be provided to allow this function to be canceled via an
|
||||||
|
* existing SIGINT signal handler that will longjmp to the specified place
|
||||||
|
* only when *(prompt_ctx->enabled) is true. If canceled, this function
|
||||||
|
* returns an empty string, and prompt_ctx->canceled is set to true.
|
||||||
|
*/
|
||||||
|
char *
|
||||||
|
simple_prompt_extended(const char *prompt, bool echo,
|
||||||
|
PromptInterruptContext *prompt_ctx)
|
||||||
{
|
{
|
||||||
char *result;
|
char *result;
|
||||||
FILE *termin,
|
FILE *termin,
|
||||||
@ -126,7 +142,7 @@ simple_prompt(const char *prompt, bool echo)
|
|||||||
fflush(termout);
|
fflush(termout);
|
||||||
}
|
}
|
||||||
|
|
||||||
result = pg_get_line(termin);
|
result = pg_get_line(termin, prompt_ctx);
|
||||||
|
|
||||||
/* If we failed to read anything, just return an empty string */
|
/* If we failed to read anything, just return an empty string */
|
||||||
if (result == NULL)
|
if (result == NULL)
|
||||||
|
@ -12,6 +12,14 @@
|
|||||||
|
|
||||||
struct StringInfoData; /* avoid including stringinfo.h here */
|
struct StringInfoData; /* avoid including stringinfo.h here */
|
||||||
|
|
||||||
|
typedef struct PromptInterruptContext
|
||||||
|
{
|
||||||
|
/* To avoid including <setjmp.h> here, jmpbuf is declared "void *" */
|
||||||
|
void *jmpbuf; /* existing longjmp buffer */
|
||||||
|
volatile bool *enabled; /* flag that enables longjmp-on-interrupt */
|
||||||
|
bool canceled; /* indicates whether cancellation occurred */
|
||||||
|
} PromptInterruptContext;
|
||||||
|
|
||||||
/* functions in src/common/string.c */
|
/* functions in src/common/string.c */
|
||||||
extern bool pg_str_endswith(const char *str, const char *end);
|
extern bool pg_str_endswith(const char *str, const char *end);
|
||||||
extern int strtoint(const char *pg_restrict str, char **pg_restrict endptr,
|
extern int strtoint(const char *pg_restrict str, char **pg_restrict endptr,
|
||||||
@ -21,11 +29,14 @@ extern int pg_strip_crlf(char *str);
|
|||||||
extern bool pg_is_ascii(const char *str);
|
extern bool pg_is_ascii(const char *str);
|
||||||
|
|
||||||
/* functions in src/common/pg_get_line.c */
|
/* functions in src/common/pg_get_line.c */
|
||||||
extern char *pg_get_line(FILE *stream);
|
extern char *pg_get_line(FILE *stream, PromptInterruptContext *prompt_ctx);
|
||||||
extern bool pg_get_line_buf(FILE *stream, struct StringInfoData *buf);
|
extern bool pg_get_line_buf(FILE *stream, struct StringInfoData *buf);
|
||||||
extern bool pg_get_line_append(FILE *stream, struct StringInfoData *buf);
|
extern bool pg_get_line_append(FILE *stream, struct StringInfoData *buf,
|
||||||
|
PromptInterruptContext *prompt_ctx);
|
||||||
|
|
||||||
/* functions in src/common/sprompt.c */
|
/* functions in src/common/sprompt.c */
|
||||||
extern char *simple_prompt(const char *prompt, bool echo);
|
extern char *simple_prompt(const char *prompt, bool echo);
|
||||||
|
extern char *simple_prompt_extended(const char *prompt, bool echo,
|
||||||
|
PromptInterruptContext *prompt_ctx);
|
||||||
|
|
||||||
#endif /* COMMON_STRING_H */
|
#endif /* COMMON_STRING_H */
|
||||||
|
Loading…
Reference in New Issue
Block a user