summaryrefslogtreecommitdiffstats
path: root/src/bin/psql/input.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-04 12:15:05 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-04 12:15:05 +0000
commit46651ce6fe013220ed397add242004d764fc0153 (patch)
tree6e5299f990f88e60174a1d3ae6e48eedd2688b2b /src/bin/psql/input.c
parentInitial commit. (diff)
downloadpostgresql-14-46651ce6fe013220ed397add242004d764fc0153.tar.xz
postgresql-14-46651ce6fe013220ed397add242004d764fc0153.zip
Adding upstream version 14.5.upstream/14.5upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/bin/psql/input.c')
-rw-r--r--src/bin/psql/input.c545
1 files changed, 545 insertions, 0 deletions
diff --git a/src/bin/psql/input.c b/src/bin/psql/input.c
new file mode 100644
index 0000000..f926bc9
--- /dev/null
+++ b/src/bin/psql/input.c
@@ -0,0 +1,545 @@
+/*
+ * psql - the PostgreSQL interactive terminal
+ *
+ * Copyright (c) 2000-2021, PostgreSQL Global Development Group
+ *
+ * src/bin/psql/input.c
+ */
+#include "postgres_fe.h"
+
+#ifndef WIN32
+#include <unistd.h>
+#endif
+#include <fcntl.h>
+#include <limits.h>
+
+#include "common.h"
+#include "common/logging.h"
+#include "input.h"
+#include "settings.h"
+#include "tab-complete.h"
+
+#ifndef WIN32
+#define PSQLHISTORY ".psql_history"
+#else
+#define PSQLHISTORY "psql_history"
+#endif
+
+/* Runtime options for turning off readline and history */
+/* (of course there is no runtime command for doing that :) */
+#ifdef USE_READLINE
+static bool useReadline;
+static bool useHistory;
+
+static char *psql_history;
+
+static int history_lines_added;
+
+
+/*
+ * Preserve newlines in saved queries by mapping '\n' to NL_IN_HISTORY
+ *
+ * It is assumed NL_IN_HISTORY will never be entered by the user
+ * nor appear inside a multi-byte string. 0x00 is not properly
+ * handled by the readline routines so it can not be used
+ * for this purpose.
+ */
+#define NL_IN_HISTORY 0x01
+#endif
+
+static void finishInput(void);
+
+
+/*
+ * gets_interactive()
+ *
+ * Gets a line of interactive input, using readline if desired.
+ *
+ * prompt: the prompt string to be used
+ * query_buf: buffer containing lines already read in the current command
+ * (query_buf is not modified here, but may be consulted for tab completion)
+ *
+ * The result is a malloc'd string.
+ *
+ * Caller *must* have set up sigint_interrupt_jmp before calling.
+ */
+char *
+gets_interactive(const char *prompt, PQExpBuffer query_buf)
+{
+#ifdef USE_READLINE
+ if (useReadline)
+ {
+ char *result;
+
+ /*
+ * Some versions of readline don't notice SIGWINCH signals that arrive
+ * when not actively reading input. The simplest fix is to always
+ * re-read the terminal size. This leaves a window for SIGWINCH to be
+ * missed between here and where readline() enables libreadline's
+ * signal handler, but that's probably short enough to be ignored.
+ */
+#ifdef HAVE_RL_RESET_SCREEN_SIZE
+ rl_reset_screen_size();
+#endif
+
+ /* Make current query_buf available to tab completion callback */
+ tab_completion_query_buf = query_buf;
+
+ /* Enable SIGINT to longjmp to sigint_interrupt_jmp */
+ sigint_interrupt_enabled = true;
+
+ /* On some platforms, readline is declared as readline(char *) */
+ result = readline((char *) prompt);
+
+ /* Disable SIGINT again */
+ sigint_interrupt_enabled = false;
+
+ /* Pure neatnik-ism */
+ tab_completion_query_buf = NULL;
+
+ return result;
+ }
+#endif
+
+ fputs(prompt, stdout);
+ fflush(stdout);
+ return gets_fromFile(stdin);
+}
+
+
+/*
+ * Append the line to the history buffer, making sure there is a trailing '\n'
+ */
+void
+pg_append_history(const char *s, PQExpBuffer history_buf)
+{
+#ifdef USE_READLINE
+ if (useHistory && s)
+ {
+ appendPQExpBufferStr(history_buf, s);
+ if (!s[0] || s[strlen(s) - 1] != '\n')
+ appendPQExpBufferChar(history_buf, '\n');
+ }
+#endif
+}
+
+
+/*
+ * Emit accumulated history entry to readline's history mechanism,
+ * then reset the buffer to empty.
+ *
+ * Note: we write nothing if history_buf is empty, so extra calls to this
+ * function don't hurt. There must have been at least one line added by
+ * pg_append_history before we'll do anything.
+ */
+void
+pg_send_history(PQExpBuffer history_buf)
+{
+#ifdef USE_READLINE
+ static char *prev_hist = NULL;
+
+ char *s = history_buf->data;
+ int i;
+
+ /* Trim any trailing \n's (OK to scribble on history_buf) */
+ for (i = strlen(s) - 1; i >= 0 && s[i] == '\n'; i--)
+ ;
+ s[i + 1] = '\0';
+
+ if (useHistory && s[0])
+ {
+ if (((pset.histcontrol & hctl_ignorespace) &&
+ s[0] == ' ') ||
+ ((pset.histcontrol & hctl_ignoredups) &&
+ prev_hist && strcmp(s, prev_hist) == 0))
+ {
+ /* Ignore this line as far as history is concerned */
+ }
+ else
+ {
+ /* Save each previous line for ignoredups processing */
+ if (prev_hist)
+ free(prev_hist);
+ prev_hist = pg_strdup(s);
+ /* And send it to readline */
+ add_history(s);
+ /* Count lines added to history for use later */
+ history_lines_added++;
+ }
+ }
+
+ resetPQExpBuffer(history_buf);
+#endif
+}
+
+
+/*
+ * gets_fromFile
+ *
+ * Gets a line of noninteractive input from a file (which could be stdin).
+ * The result is a malloc'd string, or NULL on EOF or input error.
+ *
+ * Caller *must* have set up sigint_interrupt_jmp before calling.
+ *
+ * Note: we re-use a static PQExpBuffer for each call. This is to avoid
+ * leaking memory if interrupted by SIGINT.
+ */
+char *
+gets_fromFile(FILE *source)
+{
+ static PQExpBuffer buffer = NULL;
+
+ char line[1024];
+
+ if (buffer == NULL) /* first time through? */
+ buffer = createPQExpBuffer();
+ else
+ resetPQExpBuffer(buffer);
+
+ for (;;)
+ {
+ char *result;
+
+ /* Enable SIGINT to longjmp to sigint_interrupt_jmp */
+ sigint_interrupt_enabled = true;
+
+ /* Get some data */
+ result = fgets(line, sizeof(line), source);
+
+ /* Disable SIGINT again */
+ sigint_interrupt_enabled = false;
+
+ /* EOF or error? */
+ if (result == NULL)
+ {
+ if (ferror(source))
+ {
+ pg_log_error("could not read from input file: %m");
+ return NULL;
+ }
+ break;
+ }
+
+ appendPQExpBufferStr(buffer, line);
+
+ if (PQExpBufferBroken(buffer))
+ {
+ pg_log_error("out of memory");
+ return NULL;
+ }
+
+ /* EOL? */
+ if (buffer->len > 0 && buffer->data[buffer->len - 1] == '\n')
+ {
+ buffer->data[buffer->len - 1] = '\0';
+ return pg_strdup(buffer->data);
+ }
+ }
+
+ if (buffer->len > 0) /* EOF after reading some bufferload(s) */
+ return pg_strdup(buffer->data);
+
+ /* EOF, so return null */
+ return NULL;
+}
+
+
+#ifdef USE_READLINE
+
+/*
+ * Macros to iterate over each element of the history list in order
+ *
+ * You would think this would be simple enough, but in its inimitable fashion
+ * libedit has managed to break it: in libreadline we must use next_history()
+ * to go from oldest to newest, but in libedit we must use previous_history().
+ * To detect what to do, we make a trial call of previous_history(): if it
+ * fails, then either next_history() is what to use, or there's zero or one
+ * history entry so that it doesn't matter which direction we go.
+ *
+ * In case that wasn't disgusting enough: the code below is not as obvious as
+ * it might appear. In some libedit releases history_set_pos(0) fails until
+ * at least one add_history() call has been done. This is not an issue for
+ * printHistory() or encode_history(), which cannot be invoked before that has
+ * happened. In decode_history(), that's not so, and what actually happens is
+ * that we are sitting on the newest entry to start with, previous_history()
+ * fails, and we iterate over all the entries using next_history(). So the
+ * decode_history() loop iterates over the entries in the wrong order when
+ * using such a libedit release, and if there were another attempt to use
+ * BEGIN_ITERATE_HISTORY() before some add_history() call had happened, it
+ * wouldn't work. Fortunately we don't care about either of those things.
+ *
+ * Usage pattern is:
+ *
+ * BEGIN_ITERATE_HISTORY(varname);
+ * {
+ * loop body referencing varname->line;
+ * }
+ * END_ITERATE_HISTORY();
+ */
+#define BEGIN_ITERATE_HISTORY(VARNAME) \
+ do { \
+ HIST_ENTRY *VARNAME; \
+ bool use_prev_; \
+ \
+ history_set_pos(0); \
+ use_prev_ = (previous_history() != NULL); \
+ history_set_pos(0); \
+ for (VARNAME = current_history(); VARNAME != NULL; \
+ VARNAME = use_prev_ ? previous_history() : next_history()) \
+ { \
+ (void) 0
+
+#define END_ITERATE_HISTORY() \
+ } \
+ } while(0)
+
+
+/*
+ * Convert newlines to NL_IN_HISTORY for safe saving in readline history file
+ */
+static void
+encode_history(void)
+{
+ BEGIN_ITERATE_HISTORY(cur_hist);
+ {
+ char *cur_ptr;
+
+ /* some platforms declare HIST_ENTRY.line as const char * */
+ for (cur_ptr = (char *) cur_hist->line; *cur_ptr; cur_ptr++)
+ {
+ if (*cur_ptr == '\n')
+ *cur_ptr = NL_IN_HISTORY;
+ }
+ }
+ END_ITERATE_HISTORY();
+}
+
+/*
+ * Reverse the above encoding
+ */
+static void
+decode_history(void)
+{
+ BEGIN_ITERATE_HISTORY(cur_hist);
+ {
+ char *cur_ptr;
+
+ /* some platforms declare HIST_ENTRY.line as const char * */
+ for (cur_ptr = (char *) cur_hist->line; *cur_ptr; cur_ptr++)
+ {
+ if (*cur_ptr == NL_IN_HISTORY)
+ *cur_ptr = '\n';
+ }
+ }
+ END_ITERATE_HISTORY();
+}
+#endif /* USE_READLINE */
+
+
+/*
+ * Put any startup stuff related to input in here. It's good to maintain
+ * abstraction this way.
+ *
+ * The only "flag" right now is 1 for use readline & history.
+ */
+void
+initializeInput(int flags)
+{
+#ifdef USE_READLINE
+ if (flags & 1)
+ {
+ const char *histfile;
+ char home[MAXPGPATH];
+
+ useReadline = true;
+
+ /* these two things must be done in this order: */
+ initialize_readline();
+ rl_initialize();
+
+ useHistory = true;
+ using_history();
+ history_lines_added = 0;
+
+ histfile = GetVariable(pset.vars, "HISTFILE");
+
+ if (histfile == NULL)
+ {
+ char *envhist;
+
+ envhist = getenv("PSQL_HISTORY");
+ if (envhist != NULL && strlen(envhist) > 0)
+ histfile = envhist;
+ }
+
+ if (histfile == NULL)
+ {
+ if (get_home_path(home))
+ psql_history = psprintf("%s/%s", home, PSQLHISTORY);
+ }
+ else
+ {
+ psql_history = pg_strdup(histfile);
+ expand_tilde(&psql_history);
+ }
+
+ if (psql_history)
+ {
+ read_history(psql_history);
+ decode_history();
+ }
+ }
+#endif
+
+ atexit(finishInput);
+}
+
+
+/*
+ * This function saves the readline history when psql exits.
+ *
+ * fname: pathname of history file. (Should really be "const char *",
+ * but some ancient versions of readline omit the const-decoration.)
+ *
+ * max_lines: if >= 0, limit history file to that many entries.
+ */
+#ifdef USE_READLINE
+static bool
+saveHistory(char *fname, int max_lines)
+{
+ int errnum;
+
+ /*
+ * Suppressing the write attempt when HISTFILE is set to /dev/null may
+ * look like a negligible optimization, but it's necessary on e.g. macOS,
+ * where write_history will fail because it tries to chmod the target
+ * file.
+ */
+ if (strcmp(fname, DEVNULL) != 0)
+ {
+ /*
+ * Encode \n, since otherwise readline will reload multiline history
+ * entries as separate lines. (libedit doesn't really need this, but
+ * we do it anyway since it's too hard to tell which implementation we
+ * are using.)
+ */
+ encode_history();
+
+ /*
+ * On newer versions of libreadline, truncate the history file as
+ * needed and then append what we've added. This avoids overwriting
+ * history from other concurrent sessions (although there are still
+ * race conditions when two sessions exit at about the same time). If
+ * we don't have those functions, fall back to write_history().
+ */
+#if defined(HAVE_HISTORY_TRUNCATE_FILE) && defined(HAVE_APPEND_HISTORY)
+ {
+ int nlines;
+ int fd;
+
+ /* truncate previous entries if needed */
+ if (max_lines >= 0)
+ {
+ nlines = Max(max_lines - history_lines_added, 0);
+ (void) history_truncate_file(fname, nlines);
+ }
+ /* append_history fails if file doesn't already exist :-( */
+ fd = open(fname, O_CREAT | O_WRONLY | PG_BINARY, 0600);
+ if (fd >= 0)
+ close(fd);
+ /* append the appropriate number of lines */
+ if (max_lines >= 0)
+ nlines = Min(max_lines, history_lines_added);
+ else
+ nlines = history_lines_added;
+ errnum = append_history(nlines, fname);
+ if (errnum == 0)
+ return true;
+ }
+#else /* don't have append support */
+ {
+ /* truncate what we have ... */
+ if (max_lines >= 0)
+ stifle_history(max_lines);
+ /* ... and overwrite file. Tough luck for concurrent sessions. */
+ errnum = write_history(fname);
+ if (errnum == 0)
+ return true;
+ }
+#endif
+
+ pg_log_error("could not save history to file \"%s\": %m", fname);
+ }
+ return false;
+}
+#endif
+
+
+
+/*
+ * Print history to the specified file, or to the console if fname is NULL
+ * (psql \s command)
+ *
+ * We used to use saveHistory() for this purpose, but that doesn't permit
+ * use of a pager; moreover libedit's implementation behaves incompatibly
+ * (preferring to encode its output) and may fail outright when the target
+ * file is specified as /dev/tty.
+ */
+bool
+printHistory(const char *fname, unsigned short int pager)
+{
+#ifdef USE_READLINE
+ FILE *output;
+ bool is_pager;
+
+ if (!useHistory)
+ return false;
+
+ if (fname == NULL)
+ {
+ /* use pager, if enabled, when printing to console */
+ output = PageOutput(INT_MAX, pager ? &(pset.popt.topt) : NULL);
+ is_pager = true;
+ }
+ else
+ {
+ output = fopen(fname, "w");
+ if (output == NULL)
+ {
+ pg_log_error("could not save history to file \"%s\": %m", fname);
+ return false;
+ }
+ is_pager = false;
+ }
+
+ BEGIN_ITERATE_HISTORY(cur_hist);
+ {
+ fprintf(output, "%s\n", cur_hist->line);
+ }
+ END_ITERATE_HISTORY();
+
+ if (is_pager)
+ ClosePager(output);
+ else
+ fclose(output);
+
+ return true;
+#else
+ pg_log_error("history is not supported by this installation");
+ return false;
+#endif
+}
+
+
+static void
+finishInput(void)
+{
+#ifdef USE_READLINE
+ if (useHistory && psql_history)
+ {
+ (void) saveHistory(psql_history, pset.histsize);
+ free(psql_history);
+ psql_history = NULL;
+ }
+#endif
+}