Impressum input.c
Interaktion und PortierbarkeitC
/*
* 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 . 0 x00 is not properly
* handled by the readline routines so it can not be used
* for this purpose .
*/
#define NL_IN_HISTORY 0 x01
#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
}
Messung V0.5 in Prozent C=67 H=95 G=81
¤ Diese beiden folgenden Angebotsgruppen bietet das Unternehmen0.5Angebot
(Wie Sie bei der Firma Beratungs- und Dienstleistungen beauftragen können 2026-08-09)
¤
*Eine klare Vorstellung vom Zielzustand
2026-08-25
Neuigkeiten
Aktuelles
Motto des Tages
Open Source Software
Jenseits des Üblichen ....
Besucherstatistik
Statistik
#Sources=141584
#Domains=752002