/*
 * psql - the PostgreSQL interactive terminal
 *
 * Copyright (c) 2000-2025, 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;

		result = readline(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 */
			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;

		/* set appropriate values for Readline's global variables */
		initialize_readline();

#ifdef HAVE_RL_VARIABLE_BIND
		/* set comment-begin to a useful value for SQL */
		(void) rl_variable_bind("comment-begin", "-- ");
#endif

		/* this reads ~/.inputrc, so do it after rl_variable_bind */
		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
}
