Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit44c5183

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 libeditversions when printing to the terminal, because libedit tries to do anfchmod() 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 toencode the command history file (to ensure successful reloading), whichrenders it nigh unreadable, not to mention significantly different-lookingdepending on exactly which libedit version you have. So let's forget usingwrite_history() for this purpose, and instead print the data ourselves,using logic similar to that used to iterate over the history for newlineencoding/decoding purposes.While we're at it, insert the ability to use the pager when \s is printingto the terminal. This has been an acknowledged shortcoming of \s for manyyears, so while you could argue it's not exactly a back-patchable bug fixit still seems like a good improvement. Anyone who's seriously annoyedat this can use "\s /dev/tty" or local equivalent to get the old behavior.Experimentation with this showed that the history iteration logic wasactually rather broken when used with libedit. It turns out that withlibedit you have to use previous_history() not next_history() to advanceto more recent history entries. The easiest and most robust fix for thisseems 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 newlineencoding logic: its own encoding ensures that command entries containingnewlines are reloaded correctly (unlike libreadline). So the effectivebehavior with recent libedits was that only the oldest history entry gotnewline-encoded or newline-decoded. However, because of yet other bugs inhistory_set_pos(), some old versions of libedit allowed the existing looplogic to reach entries besides the oldest, which means there may be libedit~/.psql_history files out there containing encoded newlines in more thanjust the oldest entry. To ensure we can reload such files, it seemsappropriate to back-patch this fix, even though that will result in someincompatibility with older psql versions (ie, multiline history entrieswritten 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 parentdd5b3f8 commit44c5183

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
@@ -261,7 +261,8 @@ EOF
261261
<term><option>--no-readline</></term>
262262
<listitem>
263263
<para>
264-
Do not use readline for line editing and do not use the history.
264+
Do not use <application>Readline</application> for line editing and do
265+
not use the command history.
265266
This can be useful to turn off tab expansion when cutting and pasting.
266267
</para>
267268
</listitem>
@@ -2072,12 +2073,13 @@ lo_import 152801
20722073
<term><literal>\s [ <replaceable class="parameter">filename</replaceable> ]</literal></term>
20732074
<listitem>
20742075
<para>
2075-
Print or save the command line history to <replaceable
2076-
class="parameter">filename</replaceable>. If <replaceable
2077-
class="parameter">filename</replaceable> is omitted, the history
2078-
is written to the standard output. This option is only available
2079-
if <application>psql</application> is configured to use the
2080-
<acronym>GNU</acronym> <application>Readline</application> library.
2076+
Print <application>psql</application>'s command line history
2077+
to <replaceable class="parameter">filename</replaceable>.
2078+
If <replaceable class="parameter">filename</replaceable> is omitted,
2079+
the history is written to the standard output (using the pager if
2080+
appropriate). This command is not available
2081+
if <application>psql</application> was built
2082+
without <application>Readline</application> support.
20812083
</para>
20822084
</listitem>
20832085
</varlistentry>

‎src/bin/psql/command.c

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

942-
#if defined(WIN32)&& !defined(__CYGWIN__)
943-
944-
/*
945-
* XXX This does not work for all terminal environments or for output
946-
* containing non-ASCII characters; see comments in simple_prompt().
947-
*/
948-
#defineDEVTTY"con"
949-
#else
950-
#defineDEVTTY"/dev/tty"
951-
#endif
952-
953942
expand_tilde(&fname);
954-
/* This scrolls off the screen when using /dev/tty */
955-
success=saveHistory(fname ?fname :DEVTTY,-1, false, false);
943+
success=printHistory(fname,pset.popt.topt.pager);
956944
if (success&& !pset.quiet&&fname)
957945
printf(gettext("Wrote history to file \"%s/%s\".\n"),
958946
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"
@@ -227,23 +228,73 @@ gets_fromFile(FILE *source)
227228

228229

229230
#ifdefUSE_READLINE
231+
232+
/*
233+
* Macros to iterate over each element of the history list in order
234+
*
235+
* You would think this would be simple enough, but in its inimitable fashion
236+
* libedit has managed to break it: in libreadline we must use next_history()
237+
* to go from oldest to newest, but in libedit we must use previous_history().
238+
* To detect what to do, we make a trial call of previous_history(): if it
239+
* fails, then either next_history() is what to use, or there's zero or one
240+
* history entry so that it doesn't matter which direction we go.
241+
*
242+
* In case that wasn't disgusting enough: the code below is not as obvious as
243+
* it might appear. In some libedit releases history_set_pos(0) fails until
244+
* at least one add_history() call has been done. This is not an issue for
245+
* printHistory() or encode_history(), which cannot be invoked before that has
246+
* happened. In decode_history(), that's not so, and what actually happens is
247+
* that we are sitting on the newest entry to start with, previous_history()
248+
* fails, and we iterate over all the entries using next_history(). So the
249+
* decode_history() loop iterates over the entries in the wrong order when
250+
* using such a libedit release, and if there were another attempt to use
251+
* BEGIN_ITERATE_HISTORY() before some add_history() call had happened, it
252+
* wouldn't work. Fortunately we don't care about either of those things.
253+
*
254+
* Usage pattern is:
255+
*
256+
*BEGIN_ITERATE_HISTORY(varname);
257+
*{
258+
*loop body referencing varname->line;
259+
*}
260+
*END_ITERATE_HISTORY();
261+
*/
262+
#defineBEGIN_ITERATE_HISTORY(VARNAME) \
263+
do { \
264+
HIST_ENTRY *VARNAME; \
265+
booluse_prev_; \
266+
\
267+
history_set_pos(0); \
268+
use_prev_ = (previous_history() != NULL); \
269+
history_set_pos(0); \
270+
for (VARNAME = current_history(); VARNAME != NULL; \
271+
VARNAME = use_prev_ ? previous_history() : next_history()) \
272+
{ \
273+
(void) 0
274+
275+
#defineEND_ITERATE_HISTORY() \
276+
} \
277+
} while(0)
278+
279+
230280
/*
231281
* Convert newlines to NL_IN_HISTORY for safe saving in readline history file
232282
*/
233283
staticvoid
234284
encode_history(void)
235285
{
236-
HIST_ENTRY*cur_hist;
237-
char*cur_ptr;
238-
239-
history_set_pos(0);
240-
for (cur_hist=current_history();cur_hist;cur_hist=next_history())
286+
BEGIN_ITERATE_HISTORY(cur_hist);
241287
{
288+
char*cur_ptr;
289+
242290
/* some platforms declare HIST_ENTRY.line as const char * */
243291
for (cur_ptr= (char*)cur_hist->line;*cur_ptr;cur_ptr++)
292+
{
244293
if (*cur_ptr=='\n')
245294
*cur_ptr=NL_IN_HISTORY;
295+
}
246296
}
297+
END_ITERATE_HISTORY();
247298
}
248299

249300
/*
@@ -252,17 +303,18 @@ encode_history(void)
252303
staticvoid
253304
decode_history(void)
254305
{
255-
HIST_ENTRY*cur_hist;
256-
char*cur_ptr;
257-
258-
history_set_pos(0);
259-
for (cur_hist=current_history();cur_hist;cur_hist=next_history())
306+
BEGIN_ITERATE_HISTORY(cur_hist);
260307
{
308+
char*cur_ptr;
309+
261310
/* some platforms declare HIST_ENTRY.line as const char * */
262311
for (cur_ptr= (char*)cur_hist->line;*cur_ptr;cur_ptr++)
312+
{
263313
if (*cur_ptr==NL_IN_HISTORY)
264314
*cur_ptr='\n';
315+
}
265316
}
317+
END_ITERATE_HISTORY();
266318
}
267319
#endif/* USE_READLINE */
268320

@@ -322,22 +374,15 @@ initializeInput(int flags)
322374

323375

324376
/*
325-
* This function saves the readline history when user
326-
* runs \s command or when psql exits.
377+
* This function saves the readline history when psql exits.
327378
*
328379
* fname: pathname of history file. (Should really be "const char *",
329380
* but some ancient versions of readline omit the const-decoration.)
330381
*
331382
* max_lines: if >= 0, limit history file to that many entries.
332-
*
333-
* appendFlag: if true, try to append just our new lines to the file.
334-
* If false, write the whole available history.
335-
*
336-
* encodeFlag: whether to encode \n as \x01. For \s calls we don't wish
337-
* to do that, but must do so when saving the final history file.
338383
*/
339-
bool
340-
saveHistory(char*fname,intmax_lines,boolappendFlag,boolencodeFlag)
384+
staticbool
385+
saveHistory(char*fname,intmax_lines)
341386
{
342387
#ifdefUSE_READLINE
343388

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

356405
/*
357406
* On newer versions of libreadline, truncate the history file as
@@ -365,7 +414,6 @@ saveHistory(char *fname, int max_lines, bool appendFlag, bool encodeFlag)
365414
* see if the write failed. Similarly for append_history.
366415
*/
367416
#if defined(HAVE_HISTORY_TRUNCATE_FILE)&& defined(HAVE_APPEND_HISTORY)
368-
if (appendFlag)
369417
{
370418
intnlines;
371419
intfd;
@@ -390,8 +438,7 @@ saveHistory(char *fname, int max_lines, bool appendFlag, bool encodeFlag)
390438
if (errno==0)
391439
return true;
392440
}
393-
else
394-
#endif
441+
#else/* don't have append support */
395442
{
396443
/* truncate what we have ... */
397444
if (max_lines >=0)
@@ -402,19 +449,73 @@ saveHistory(char *fname, int max_lines, bool appendFlag, bool encodeFlag)
402449
if (errno==0)
403450
return true;
404451
}
452+
#endif
405453

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

414459
return false;
415460
}
416461

417462

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

430531
hist_size=GetVariableNum(pset.vars,"HISTSIZE",500,-1, true);
431-
saveHistory(psql_history,hist_size, true, true);
532+
(void)saveHistory(psql_history,hist_size);
432533
free(psql_history);
433534
psql_history=NULL;
434535
}

‎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
voidinitializeInput(intflags);
45-
boolsaveHistory(char*fname,intmax_lines,boolappendFlag,boolencodeFlag);
45+
46+
boolprintHistory(constchar*fname,unsigned shortintpager);
4647

4748
voidpg_append_history(constchar*s,PQExpBufferhistory_buf);
4849
voidpg_send_history(PQExpBufferhistory_buf);

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp