postgresql/src/common/sprompt.c
Tom Lane 46d665bc26 Allow psql's other uses of simple_prompt() to be interrupted by ^C.
This fills in the work left un-done by 5f1148224.  \prompt can
be canceled out of now, and so can password prompts issued during
\connect.  (We don't need to do anything for password prompts
issued during startup, because we aren't yet trapping SIGINT
at that point.)

Nathan Bossart

Discussion: https://postgr.es/m/747443.1635536754@sss.pgh.pa.us
2021-11-19 12:11:46 -05:00

181 lines
4.8 KiB
C

/*-------------------------------------------------------------------------
*
* sprompt.c
* simple_prompt() routine
*
* Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
*
* IDENTIFICATION
* src/common/sprompt.c
*
*-------------------------------------------------------------------------
*/
#include "c.h"
#include "common/fe_memutils.h"
#include "common/string.h"
#ifdef HAVE_TERMIOS_H
#include <termios.h>
#endif
/*
* simple_prompt
*
* Generalized function especially intended for reading in usernames and
* passwords interactively. Reads from /dev/tty or stdin/stderr.
*
* prompt: The prompt to print, or NULL if none (automatically localized)
* echo: Set to false if you want to hide what is entered (for passwords)
*
* The input (without trailing newline) is returned as a malloc'd string.
* Caller is responsible for freeing it when done.
*/
char *
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;
FILE *termin,
*termout;
#if defined(HAVE_TERMIOS_H)
struct termios t_orig,
t;
#elif defined(WIN32)
HANDLE t = NULL;
DWORD t_orig = 0;
#endif
#ifdef WIN32
/*
* A Windows console has an "input code page" and an "output code page";
* these usually match each other, but they rarely match the "Windows ANSI
* code page" defined at system boot and expected of "char *" arguments to
* Windows API functions. The Microsoft CRT write() implementation
* automatically converts text between these code pages when writing to a
* console. To identify such file descriptors, it calls GetConsoleMode()
* on the underlying HANDLE, which in turn requires GENERIC_READ access on
* the HANDLE. Opening termout in mode "w+" allows that detection to
* succeed. Otherwise, write() would not recognize the descriptor as a
* console, and non-ASCII characters would display incorrectly.
*
* XXX fgets() still receives text in the console's input code page. This
* makes non-ASCII credentials unportable.
*
* Unintuitively, we also open termin in mode "w+", even though we only
* read it; that's needed for SetConsoleMode() to succeed.
*/
termin = fopen("CONIN$", "w+");
termout = fopen("CONOUT$", "w+");
#else
/*
* Do not try to collapse these into one "w+" mode file. Doesn't work on
* some platforms (eg, HPUX 10.20).
*/
termin = fopen("/dev/tty", "r");
termout = fopen("/dev/tty", "w");
#endif
if (!termin || !termout
#ifdef WIN32
/*
* Direct console I/O does not work from the MSYS 1.0.10 console. Writes
* reach nowhere user-visible; reads block indefinitely. XXX This affects
* most Windows terminal environments, including rxvt, mintty, Cygwin
* xterm, Cygwin sshd, and PowerShell ISE. Switch to a more-generic test.
*/
|| (getenv("OSTYPE") && strcmp(getenv("OSTYPE"), "msys") == 0)
#endif
)
{
if (termin)
fclose(termin);
if (termout)
fclose(termout);
termin = stdin;
termout = stderr;
}
if (!echo)
{
#if defined(HAVE_TERMIOS_H)
/* disable echo via tcgetattr/tcsetattr */
tcgetattr(fileno(termin), &t);
t_orig = t;
t.c_lflag &= ~ECHO;
tcsetattr(fileno(termin), TCSAFLUSH, &t);
#elif defined(WIN32)
/* need the file's HANDLE to turn echo off */
t = (HANDLE) _get_osfhandle(_fileno(termin));
/* save the old configuration first */
GetConsoleMode(t, &t_orig);
/* set to the new mode */
SetConsoleMode(t, ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT);
#endif
}
if (prompt)
{
fputs(_(prompt), termout);
fflush(termout);
}
result = pg_get_line(termin, prompt_ctx);
/* If we failed to read anything, just return an empty string */
if (result == NULL)
result = pg_strdup("");
/* strip trailing newline, including \r in case we're on Windows */
(void) pg_strip_crlf(result);
if (!echo)
{
/* restore previous echo behavior, then echo \n */
#if defined(HAVE_TERMIOS_H)
tcsetattr(fileno(termin), TCSAFLUSH, &t_orig);
fputs("\n", termout);
fflush(termout);
#elif defined(WIN32)
SetConsoleMode(t, t_orig);
fputs("\n", termout);
fflush(termout);
#endif
}
else if (prompt_ctx && prompt_ctx->canceled)
{
/* also echo \n if prompt was canceled */
fputs("\n", termout);
fflush(termout);
}
if (termin != stdin)
{
fclose(termin);
fclose(termout);
}
return result;
}