Skip to content

Commit 886b58b

Browse files
committed
Fix psql \s to work with recent libedit, and add pager support.
psql's \s (print command history) doesn't work at all with recent libedit versions when printing to the terminal, because libedit tries to do an fchmod() on the target file which will fail if the target is /dev/tty. (We'd already noted this in the context of the target being /dev/null.) Even before that, it didn't work pleasantly, because libedit likes to encode the command history file (to ensure successful reloading), which renders it nigh unreadable, not to mention significantly different-looking depending on exactly which libedit version you have. So let's forget using write_history() for this purpose, and instead print the data ourselves, using logic similar to that used to iterate over the history for newline encoding/decoding purposes. While we're at it, insert the ability to use the pager when \s is printing to the terminal. This has been an acknowledged shortcoming of \s for many years, so while you could argue it's not exactly a back-patchable bug fix it still seems like a good improvement. Anyone who's seriously annoyed at this can use "\s /dev/tty" or local equivalent to get the old behavior. Experimentation with this showed that the history iteration logic was actually rather broken when used with libedit. It turns out that with libedit you have to use previous_history() not next_history() to advance to more recent history entries. The easiest and most robust fix for this seems to be to make a run-time test to verify which function to call. We had not noticed this because libedit doesn't really need the newline encoding logic: its own encoding ensures that command entries containing newlines are reloaded correctly (unlike libreadline). So the effective behavior with recent libedits was that only the oldest history entry got newline-encoded or newline-decoded. However, because of yet other bugs in history_set_pos(), some old versions of libedit allowed the existing loop logic to reach entries besides the oldest, which means there may be libedit ~/.psql_history files out there containing encoded newlines in more than just the oldest entry. To ensure we can reload such files, it seems appropriate to back-patch this fix, even though that will result in some incompatibility with older psql versions (ie, multiline history entries written by a psql with this fix will look corrupted to a psql without it, if its libedit is reasonably up to date). Stepan Rutz and Tom Lane
1 parent 4cadb8a commit 886b58b

File tree

4 files changed

+144
-52
lines changed

4 files changed

+144
-52
lines changed

doc/src/sgml/ref/psql-ref.sgml

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -264,7 +264,8 @@ EOF
264264
<term><option>--no-readline</></term>
265265
<listitem>
266266
<para>
267-
Do not use <application>readline</application> for line editing and do not use the history.
267+
Do not use <application>Readline</application> for line editing and do
268+
not use the command history.
268269
This can be useful to turn off tab expansion when cutting and pasting.
269270
</para>
270271
</listitem>
@@ -2255,12 +2256,13 @@ lo_import 152801
22552256
<term><literal>\s [ <replaceable class="parameter">filename</replaceable> ]</literal></term>
22562257
<listitem>
22572258
<para>
2258-
Print or save the command line history to <replaceable
2259-
class="parameter">filename</replaceable>. If <replaceable
2260-
class="parameter">filename</replaceable> is omitted, the history
2261-
is written to the standard output. This option is only available
2262-
if <application>psql</application> is configured to use the
2263-
<acronym>GNU</acronym> <application>Readline</application> library.
2259+
Print <application>psql</application>'s command line history
2260+
to <replaceable class="parameter">filename</replaceable>.
2261+
If <replaceable class="parameter">filename</replaceable> is omitted,
2262+
the history is written to the standard output (using the pager if
2263+
appropriate). This command is not available
2264+
if <application>psql</application> was built
2265+
without <application>Readline</application> support.
22642266
</para>
22652267
</listitem>
22662268
</varlistentry>

src/bin/psql/command.c

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1039,20 +1039,8 @@ exec_command(const char *cmd,
10391039
char *fname = psql_scan_slash_option(scan_state,
10401040
OT_NORMAL, NULL, true);
10411041

1042-
#if defined(WIN32) && !defined(__CYGWIN__)
1043-
1044-
/*
1045-
* XXX This does not work for all terminal environments or for output
1046-
* containing non-ASCII characters; see comments in simple_prompt().
1047-
*/
1048-
#define DEVTTY "con"
1049-
#else
1050-
#define DEVTTY "/dev/tty"
1051-
#endif
1052-
10531042
expand_tilde(&fname);
1054-
/* This scrolls off the screen when using /dev/tty */
1055-
success = saveHistory(fname ? fname : DEVTTY, -1, false, false);
1043+
success = printHistory(fname, pset.popt.topt.pager);
10561044
if (success && !pset.quiet && fname)
10571045
printf(gettext("Wrote history to file \"%s/%s\".\n"),
10581046
pset.dirname ? pset.dirname : ".", fname);

src/bin/psql/input.c

Lines changed: 132 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
#include <unistd.h>
1212
#endif
1313
#include <fcntl.h>
14+
#include <limits.h>
1415

1516
#include "input.h"
1617
#include "settings.h"
@@ -222,23 +223,73 @@ gets_fromFile(FILE *source)
222223

223224

224225
#ifdef USE_READLINE
226+
227+
/*
228+
* Macros to iterate over each element of the history list in order
229+
*
230+
* You would think this would be simple enough, but in its inimitable fashion
231+
* libedit has managed to break it: in libreadline we must use next_history()
232+
* to go from oldest to newest, but in libedit we must use previous_history().
233+
* To detect what to do, we make a trial call of previous_history(): if it
234+
* fails, then either next_history() is what to use, or there's zero or one
235+
* history entry so that it doesn't matter which direction we go.
236+
*
237+
* In case that wasn't disgusting enough: the code below is not as obvious as
238+
* it might appear. In some libedit releases history_set_pos(0) fails until
239+
* at least one add_history() call has been done. This is not an issue for
240+
* printHistory() or encode_history(), which cannot be invoked before that has
241+
* happened. In decode_history(), that's not so, and what actually happens is
242+
* that we are sitting on the newest entry to start with, previous_history()
243+
* fails, and we iterate over all the entries using next_history(). So the
244+
* decode_history() loop iterates over the entries in the wrong order when
245+
* using such a libedit release, and if there were another attempt to use
246+
* BEGIN_ITERATE_HISTORY() before some add_history() call had happened, it
247+
* wouldn't work. Fortunately we don't care about either of those things.
248+
*
249+
* Usage pattern is:
250+
*
251+
* BEGIN_ITERATE_HISTORY(varname);
252+
* {
253+
* loop body referencing varname->line;
254+
* }
255+
* END_ITERATE_HISTORY();
256+
*/
257+
#define BEGIN_ITERATE_HISTORY(VARNAME) \
258+
do { \
259+
HIST_ENTRY *VARNAME; \
260+
bool use_prev_; \
261+
\
262+
history_set_pos(0); \
263+
use_prev_ = (previous_history() != NULL); \
264+
history_set_pos(0); \
265+
for (VARNAME = current_history(); VARNAME != NULL; \
266+
VARNAME = use_prev_ ? previous_history() : next_history()) \
267+
{ \
268+
(void) 0
269+
270+
#define END_ITERATE_HISTORY() \
271+
} \
272+
} while(0)
273+
274+
225275
/*
226276
* Convert newlines to NL_IN_HISTORY for safe saving in readline history file
227277
*/
228278
static void
229279
encode_history(void)
230280
{
231-
HIST_ENTRY *cur_hist;
232-
char *cur_ptr;
233-
234-
history_set_pos(0);
235-
for (cur_hist = current_history(); cur_hist; cur_hist = next_history())
281+
BEGIN_ITERATE_HISTORY(cur_hist);
236282
{
283+
char *cur_ptr;
284+
237285
/* some platforms declare HIST_ENTRY.line as const char * */
238286
for (cur_ptr = (char *) cur_hist->line; *cur_ptr; cur_ptr++)
287+
{
239288
if (*cur_ptr == '\n')
240289
*cur_ptr = NL_IN_HISTORY;
290+
}
241291
}
292+
END_ITERATE_HISTORY();
242293
}
243294

244295
/*
@@ -247,17 +298,18 @@ encode_history(void)
247298
static void
248299
decode_history(void)
249300
{
250-
HIST_ENTRY *cur_hist;
251-
char *cur_ptr;
252-
253-
history_set_pos(0);
254-
for (cur_hist = current_history(); cur_hist; cur_hist = next_history())
301+
BEGIN_ITERATE_HISTORY(cur_hist);
255302
{
303+
char *cur_ptr;
304+
256305
/* some platforms declare HIST_ENTRY.line as const char * */
257306
for (cur_ptr = (char *) cur_hist->line; *cur_ptr; cur_ptr++)
307+
{
258308
if (*cur_ptr == NL_IN_HISTORY)
259309
*cur_ptr = '\n';
310+
}
260311
}
312+
END_ITERATE_HISTORY();
261313
}
262314
#endif /* USE_READLINE */
263315

@@ -323,22 +375,15 @@ initializeInput(int flags)
323375

324376

325377
/*
326-
* This function saves the readline history when user
327-
* runs \s command or when psql exits.
378+
* This function saves the readline history when psql exits.
328379
*
329380
* fname: pathname of history file. (Should really be "const char *",
330381
* but some ancient versions of readline omit the const-decoration.)
331382
*
332383
* max_lines: if >= 0, limit history file to that many entries.
333-
*
334-
* appendFlag: if true, try to append just our new lines to the file.
335-
* If false, write the whole available history.
336-
*
337-
* encodeFlag: whether to encode \n as \x01. For \s calls we don't wish
338-
* to do that, but must do so when saving the final history file.
339384
*/
340-
bool
341-
saveHistory(char *fname, int max_lines, bool appendFlag, bool encodeFlag)
385+
static bool
386+
saveHistory(char *fname, int max_lines)
342387
{
343388
#ifdef USE_READLINE
344389

@@ -348,11 +393,15 @@ saveHistory(char *fname, int max_lines, bool appendFlag, bool encodeFlag)
348393
* where write_history will fail because it tries to chmod the target
349394
* file.
350395
*/
351-
if (useHistory && fname &&
352-
strcmp(fname, DEVNULL) != 0)
396+
if (strcmp(fname, DEVNULL) != 0)
353397
{
354-
if (encodeFlag)
355-
encode_history();
398+
/*
399+
* Encode \n, since otherwise readline will reload multiline history
400+
* entries as separate lines. (libedit doesn't really need this, but
401+
* we do it anyway since it's too hard to tell which implementation we
402+
* are using.)
403+
*/
404+
encode_history();
356405

357406
/*
358407
* On newer versions of libreadline, truncate the history file as
@@ -366,7 +415,6 @@ saveHistory(char *fname, int max_lines, bool appendFlag, bool encodeFlag)
366415
* see if the write failed. Similarly for append_history.
367416
*/
368417
#if defined(HAVE_HISTORY_TRUNCATE_FILE) && defined(HAVE_APPEND_HISTORY)
369-
if (appendFlag)
370418
{
371419
int nlines;
372420
int fd;
@@ -391,8 +439,7 @@ saveHistory(char *fname, int max_lines, bool appendFlag, bool encodeFlag)
391439
if (errno == 0)
392440
return true;
393441
}
394-
else
395-
#endif
442+
#else /* don't have append support */
396443
{
397444
/* truncate what we have ... */
398445
if (max_lines >= 0)
@@ -403,19 +450,73 @@ saveHistory(char *fname, int max_lines, bool appendFlag, bool encodeFlag)
403450
if (errno == 0)
404451
return true;
405452
}
453+
#endif
406454

407455
psql_error("could not save history to file \"%s\": %s\n",
408456
fname, strerror(errno));
409457
}
410-
#else
411-
/* only get here in \s case, so complain */
412-
psql_error("history is not supported by this installation\n");
413458
#endif
414459

415460
return false;
416461
}
417462

418463

464+
/*
465+
* Print history to the specified file, or to the console if fname is NULL
466+
* (psql \s command)
467+
*
468+
* We used to use saveHistory() for this purpose, but that doesn't permit
469+
* use of a pager; moreover libedit's implementation behaves incompatibly
470+
* (preferring to encode its output) and may fail outright when the target
471+
* file is specified as /dev/tty.
472+
*/
473+
bool
474+
printHistory(const char *fname, unsigned short int pager)
475+
{
476+
#ifdef USE_READLINE
477+
FILE *output;
478+
bool is_pager;
479+
480+
if (!useHistory)
481+
return false;
482+
483+
if (fname == NULL)
484+
{
485+
/* use pager, if enabled, when printing to console */
486+
output = PageOutput(INT_MAX, pager);
487+
is_pager = true;
488+
}
489+
else
490+
{
491+
output = fopen(fname, "w");
492+
if (output == NULL)
493+
{
494+
psql_error("could not save history to file \"%s\": %s\n",
495+
fname, strerror(errno));
496+
return false;
497+
}
498+
is_pager = false;
499+
}
500+
501+
BEGIN_ITERATE_HISTORY(cur_hist);
502+
{
503+
fprintf(output, "%s\n", cur_hist->line);
504+
}
505+
END_ITERATE_HISTORY();
506+
507+
if (is_pager)
508+
ClosePager(output);
509+
else
510+
fclose(output);
511+
512+
return true;
513+
#else
514+
psql_error("history is not supported by this installation\n");
515+
return false;
516+
#endif
517+
}
518+
519+
419520
static void
420521
finishInput(void)
421522
{
@@ -425,7 +526,7 @@ finishInput(void)
425526
int hist_size;
426527

427528
hist_size = GetVariableNum(pset.vars, "HISTSIZE", 500, -1, true);
428-
saveHistory(psql_history, hist_size, true, true);
529+
(void) saveHistory(psql_history, hist_size);
429530
free(psql_history);
430531
psql_history = NULL;
431532
}

src/bin/psql/input.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,8 @@ char *gets_interactive(const char *prompt);
4242
char *gets_fromFile(FILE *source);
4343

4444
void initializeInput(int flags);
45-
bool saveHistory(char *fname, int max_lines, bool appendFlag, bool encodeFlag);
45+
46+
bool printHistory(const char *fname, unsigned short int pager);
4647

4748
void pg_append_history(const char *s, PQExpBuffer history_buf);
4849
void pg_send_history(PQExpBuffer history_buf);

0 commit comments

Comments
 (0)