/*
* psql - the PostgreSQL interactive terminal
*
- * Copyright (c) 2000-2004, PostgreSQL Global Development Group
+ * Copyright (c) 2000-2010, PostgreSQL Global Development Group
*
- * $PostgreSQL: pgsql/src/bin/psql/input.c,v 1.40 2004/10/06 19:04:10 momjian Exp $
+ * $PostgreSQL: pgsql/src/bin/psql/input.c,v 1.69 2010/02/26 02:01:18 momjian Exp $
*/
#include "postgres_fe.h"
-#include "input.h"
-#include <errno.h>
+#ifndef WIN32
+#include <unistd.h>
+#endif
+#include <fcntl.h>
-#include "pqexpbuffer.h"
+#include "input.h"
#include "settings.h"
#include "tab-complete.h"
#include "common.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;
-enum histcontrol
-{
- hctl_none = 0,
- hctl_ignorespace = 1,
- hctl_ignoredups = 2,
- hctl_ignoreboth = hctl_ignorespace | hctl_ignoredups
-};
+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
#ifdef HAVE_ATEXIT
static void finishInput(void);
-
#else
/* designed for use with on_exit() */
static void finishInput(int, void *);
#endif
-#define PSQLHISTORY ".psql_history"
-
-#ifdef USE_READLINE
-static enum histcontrol
-GetHistControlConfig(void)
+/*
+ * gets_interactive()
+ *
+ * Gets a line of interactive input, using readline if desired.
+ * The result is a malloc'd string.
+ *
+ * Caller *must* have set up sigint_interrupt_jmp before calling.
+ */
+char *
+gets_interactive(const char *prompt)
{
- enum histcontrol HC;
- const char *var;
-
- var = GetVariable(pset.vars, "HISTCONTROL");
-
- if (!var)
- HC = hctl_none;
- else if (strcmp(var, "ignorespace") == 0)
- HC = hctl_ignorespace;
- else if (strcmp(var, "ignoredups") == 0)
- HC = hctl_ignoredups;
- else if (strcmp(var, "ignoreboth") == 0)
- HC = hctl_ignoreboth;
- else
- HC = hctl_none;
+#ifdef USE_READLINE
+ if (useReadline)
+ {
+ char *result;
- return HC;
-}
-#endif
+ /* 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;
+
+ return result;
+ }
+#endif
-static char *
-gets_basic(const char prompt[])
-{
fputs(prompt, stdout);
fflush(stdout);
return gets_fromFile(stdin);
/*
- * gets_interactive()
- *
- * Gets a line of interactive input, using readline of desired.
- * The result is malloc'ed.
+ * Append the line to the history buffer, making sure there is a trailing '\n'
*/
-char *
-gets_interactive(const char *prompt)
+void
+pg_append_history(const char *s, PQExpBuffer history_buf)
{
#ifdef USE_READLINE
- char *s;
+ if (useHistory && s && s[0])
+ {
+ appendPQExpBufferStr(history_buf, s);
+ if (s[strlen(s) - 1] != '\n')
+ appendPQExpBufferChar(history_buf, '\n');
+ }
+#endif
+}
- static char *prev_hist = NULL;
- if (useReadline)
- /* On some platforms, readline is declared as readline(char *) */
- s = readline((char *) prompt);
- else
- s = gets_basic(prompt);
+/*
+ * 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;
- if (useHistory && s && s[0])
- {
- enum histcontrol HC;
+ char *s = history_buf->data;
+ int i;
- HC = GetHistControlConfig();
+ /* 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 (((HC & hctl_ignorespace) && s[0] == ' ') ||
- ((HC & hctl_ignoredups) && prev_hist && strcmp(s, prev_hist) == 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
{
- free(prev_hist);
+ /* 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++;
}
}
- return s;
-#else
- return gets_basic(prompt);
+ 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)
{
- PQExpBufferData buffer;
+ static PQExpBuffer buffer = NULL;
+
char line[1024];
- initPQExpBuffer(&buffer);
+ if (buffer == NULL) /* first time through? */
+ buffer = createPQExpBuffer();
+ else
+ resetPQExpBuffer(buffer);
- while (fgets(line, sizeof(line), source) != NULL)
+ for (;;)
{
- appendPQExpBufferStr(&buffer, line);
- if (buffer.data[buffer.len - 1] == '\n')
+ 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)
{
- buffer.data[buffer.len - 1] = '\0';
- return buffer.data;
+ if (ferror(source))
+ {
+ psql_error("could not read from input file: %s\n",
+ strerror(errno));
+ return NULL;
+ }
+ break;
+ }
+
+ appendPQExpBufferStr(buffer, line);
+
+ if (PQExpBufferBroken(buffer))
+ {
+ psql_error("out of memory\n");
+ return NULL;
+ }
+
+ /* EOL? */
+ if (buffer->data[buffer->len - 1] == '\n')
+ {
+ buffer->data[buffer->len - 1] = '\0';
+ return pg_strdup(buffer->data);
}
}
- if (buffer.len > 0)
- return buffer.data; /* EOF after reading some bufferload(s) */
+ if (buffer->len > 0) /* EOF after reading some bufferload(s) */
+ return pg_strdup(buffer->data);
/* EOF, so return null */
- termPQExpBuffer(&buffer);
return NULL;
}
+#ifdef USE_READLINE
+/*
+ * Convert newlines to NL_IN_HISTORY for safe saving in readline history file
+ */
+static void
+encode_history(void)
+{
+ HIST_ENTRY *cur_hist;
+ char *cur_ptr;
+
+ history_set_pos(0);
+ for (cur_hist = current_history(); cur_hist; cur_hist = next_history())
+ {
+ /* 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;
+ }
+}
+
+/*
+ * Reverse the above encoding
+ */
+static void
+decode_history(void)
+{
+ HIST_ENTRY *cur_hist;
+ char *cur_ptr;
+
+ history_set_pos(0);
+ for (cur_hist = current_history(); cur_hist; cur_hist = next_history())
+ {
+ /* 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';
+ }
+}
+#endif /* USE_READLINE */
+
/*
* Put any startup stuff related to input in here. It's good to maintain
#ifdef USE_READLINE
if (flags & 1)
{
+ const char *histfile;
char home[MAXPGPATH];
useReadline = true;
initialize_readline();
useHistory = true;
- if (GetVariable(pset.vars, "HISTSIZE") == NULL)
- SetVariable(pset.vars, "HISTSIZE", "500");
using_history();
- if (get_home_path(home))
+ history_lines_added = 0;
+
+ histfile = GetVariable(pset.vars, "HISTFILE");
+ if (histfile == NULL)
{
- char *psql_history;
+ if (get_home_path(home))
+ {
+ psql_history = pg_malloc(strlen(home) + 1 +
+ strlen(PSQLHISTORY) + 1);
+ snprintf(psql_history, MAXPGPATH, "%s/%s", home, PSQLHISTORY);
+ }
+ }
+ else
+ {
+ psql_history = pg_strdup(histfile);
+ expand_tilde(&psql_history);
+ }
- psql_history = pg_malloc(strlen(home) + 1 +
- strlen(PSQLHISTORY) + 1);
- sprintf(psql_history, "%s/%s", home, PSQLHISTORY);
+ if (psql_history)
+ {
read_history(psql_history);
- free(psql_history);
+ decode_history();
}
}
#endif
}
-
+/*
+ * This function saves the readline history when user
+ * runs \s command or 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.
+ *
+ * appendFlag: if true, try to append just our new lines to the file.
+ * If false, write the whole available history.
+ *
+ * encodeFlag: whether to encode \n as \x01. For \s calls we don't wish
+ * to do that, but must do so when saving the final history file.
+ */
bool
-saveHistory(char *fname)
+saveHistory(char *fname, int max_lines, bool appendFlag, bool encodeFlag)
{
#ifdef USE_READLINE
- if (useHistory && fname)
+
+ /*
+ * Suppressing the write attempt when HISTFILE is set to /dev/null may
+ * look like a negligible optimization, but it's necessary on e.g. Darwin,
+ * where write_history will fail because it tries to chmod the target
+ * file.
+ */
+ if (useHistory && fname &&
+ strcmp(fname, DEVNULL) != 0)
{
- if (write_history(fname) == 0)
- return true;
+ if (encodeFlag)
+ 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().
+ *
+ * Note: return value of write_history is not standardized across GNU
+ * readline and libedit. Therefore, check for errno becoming set to
+ * see if the write failed. Similarly for append_history.
+ */
+#if defined(HAVE_HISTORY_TRUNCATE_FILE) && defined(HAVE_APPEND_HISTORY)
+ if (appendFlag)
+ {
+ 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;
+ errno = 0;
+ (void) append_history(nlines, fname);
+ if (errno == 0)
+ return true;
+ }
+ else
+#endif
+ {
+ /* truncate what we have ... */
+ if (max_lines >= 0)
+ stifle_history(max_lines);
+ /* ... and overwrite file. Tough luck for concurrent sessions. */
+ errno = 0;
+ (void) write_history(fname);
+ if (errno == 0)
+ return true;
+ }
- psql_error("could not save history to file \"%s\": %s\n", fname, strerror(errno));
+ psql_error("could not save history to file \"%s\": %s\n",
+ fname, strerror(errno));
}
#else
- psql_error("history is not supported\n");
+ /* only get here in \s case, so complain */
+ psql_error("history is not supported by this installation\n");
#endif
return false;
}
-
static void
#ifdef HAVE_ATEXIT
finishInput(void)
#endif
{
#ifdef USE_READLINE
- if (useHistory)
+ if (useHistory && psql_history)
{
- char home[MAXPGPATH];
-
- if (get_home_path(home))
- {
- char *psql_history;
- int hist_size;
-
- psql_history = pg_malloc(strlen(home) + 1 +
- strlen(PSQLHISTORY) + 1);
+ int hist_size;
- hist_size = GetVariableNum(pset.vars, "HISTSIZE", -1, -1, true);
-
- if (hist_size >= 0)
- stifle_history(hist_size);
-
- sprintf(psql_history, "%s/%s", home, PSQLHISTORY);
- write_history(psql_history);
- free(psql_history);
- }
+ hist_size = GetVariableNum(pset.vars, "HISTSIZE", 500, -1, true);
+ saveHistory(psql_history, hist_size, true, true);
+ free(psql_history);
+ psql_history = NULL;
}
#endif
}