Clean up some psql issues around handling of the query output file.

Formerly, if "psql -o foo" failed to open the output file "foo", it would
print an error message but then carry on as though -o had not been
specified at all.  This seems contrary to expectation: a program that
cannot open its output file normally fails altogether.  Make psql do
exit(1) after reporting the error.

If "\o foo" failed to open "foo", it would print an error message but then
reset the output file to stdout, as if the argument had been omitted.
This is likewise pretty surprising behavior.  Make it keep the previous
output state, instead.

psql keeps SIGPIPE interrupts disabled when it is writing to a pipe, either
a pipe specified by -o/\o or a transient pipe opened for purposes such as
using a pager on query output.  The logic for this was too simple and could
sometimes re-enable SIGPIPE when a -o pipe was still active, thus possibly
leading to an unexpected psql crash later.

Fixing the last point required getting rid of the kluge in PrintQueryTuples
and ExecQueryUsingCursor whereby they'd transiently change the global
queryFout state, but that seems like good cleanup anyway.

Back-patch to 9.5 but not further; these are minor-enough issues that
changing the behavior in stable branches doesn't seem appropriate.
This commit is contained in:
Tom Lane 2015-12-03 14:28:58 -05:00
parent f15b820a5c
commit 344cdff2c1
7 changed files with 179 additions and 107 deletions

View File

@ -1531,6 +1531,7 @@ exec_command(const char *cmd,
if (fname[0] == '|') if (fname[0] == '|')
{ {
is_pipe = true; is_pipe = true;
disable_sigpipe_trap();
fd = popen(&fname[1], "w"); fd = popen(&fname[1], "w");
} }
else else
@ -1565,6 +1566,9 @@ exec_command(const char *cmd,
} }
} }
if (is_pipe)
restore_sigpipe_trap();
free(fname); free(fname);
} }

View File

@ -26,23 +26,66 @@
#include "mbprint.h" #include "mbprint.h"
static bool ExecQueryUsingCursor(const char *query, double *elapsed_msec); static bool ExecQueryUsingCursor(const char *query, double *elapsed_msec);
static bool command_no_begin(const char *query); static bool command_no_begin(const char *query);
static bool is_select_command(const char *query); static bool is_select_command(const char *query);
/*
* openQueryOutputFile --- attempt to open a query output file
*
* fname == NULL selects stdout, else an initial '|' selects a pipe,
* else plain file.
*
* Returns output file pointer into *fout, and is-a-pipe flag into *is_pipe.
* Caller is responsible for adjusting SIGPIPE state if it's a pipe.
*
* On error, reports suitable error message and returns FALSE.
*/
bool
openQueryOutputFile(const char *fname, FILE **fout, bool *is_pipe)
{
if (!fname || fname[0] == '\0')
{
*fout = stdout;
*is_pipe = false;
}
else if (*fname == '|')
{
*fout = popen(fname + 1, "w");
*is_pipe = true;
}
else
{
*fout = fopen(fname, "w");
*is_pipe = false;
}
if (*fout == NULL)
{
psql_error("%s: %s\n", fname, strerror(errno));
return false;
}
return true;
}
/* /*
* setQFout * setQFout
* -- handler for -o command line option and \o command * -- handler for -o command line option and \o command
* *
* Tries to open file fname (or pipe if fname starts with '|') * On success, updates pset with the new output file and returns true.
* and stores the file handle in pset) * On failure, returns false without changing pset state.
* Upon failure, sets stdout and returns false.
*/ */
bool bool
setQFout(const char *fname) setQFout(const char *fname)
{ {
bool status = true; FILE *fout;
bool is_pipe;
/* First make sure we can open the new output file/pipe */
if (!openQueryOutputFile(fname, &fout, &is_pipe))
return false;
/* Close old file/pipe */ /* Close old file/pipe */
if (pset.queryFout && pset.queryFout != stdout && pset.queryFout != stderr) if (pset.queryFout && pset.queryFout != stdout && pset.queryFout != stderr)
@ -53,45 +96,20 @@ setQFout(const char *fname)
fclose(pset.queryFout); fclose(pset.queryFout);
} }
/* If no filename, set stdout */ pset.queryFout = fout;
if (!fname || fname[0] == '\0') pset.queryFoutPipe = is_pipe;
{
pset.queryFout = stdout;
pset.queryFoutPipe = false;
}
else if (*fname == '|')
{
pset.queryFout = popen(fname + 1, "w");
pset.queryFoutPipe = true;
}
else
{
pset.queryFout = fopen(fname, "w");
pset.queryFoutPipe = false;
}
if (!(pset.queryFout)) /* Adjust SIGPIPE handling appropriately: ignore signal if is_pipe */
{ set_sigpipe_trap_state(is_pipe);
psql_error("%s: %s\n", fname, strerror(errno)); restore_sigpipe_trap();
pset.queryFout = stdout;
pset.queryFoutPipe = false;
status = false;
}
/* Direct signals */ return true;
#ifndef WIN32
pqsignal(SIGPIPE, pset.queryFoutPipe ? SIG_IGN : SIG_DFL);
#endif
return status;
} }
/* /*
* Error reporting for scripts. Errors should look like * Error reporting for scripts. Errors should look like
* psql:filename:lineno: message * psql:filename:lineno: message
*
*/ */
void void
psql_error(const char *fmt,...) psql_error(const char *fmt,...)
@ -611,27 +629,23 @@ PrintQueryTuples(const PGresult *results)
/* write output to \g argument, if any */ /* write output to \g argument, if any */
if (pset.gfname) if (pset.gfname)
{ {
/* keep this code in sync with ExecQueryUsingCursor */ FILE *fout;
FILE *queryFout_copy = pset.queryFout; bool is_pipe;
bool queryFoutPipe_copy = pset.queryFoutPipe;
pset.queryFout = stdout; /* so it doesn't get closed */ if (!openQueryOutputFile(pset.gfname, &fout, &is_pipe))
/* open file/pipe */
if (!setQFout(pset.gfname))
{
pset.queryFout = queryFout_copy;
pset.queryFoutPipe = queryFoutPipe_copy;
return false; return false;
if (is_pipe)
disable_sigpipe_trap();
printQuery(results, &my_popt, fout, false, pset.logfile);
if (is_pipe)
{
pclose(fout);
restore_sigpipe_trap();
} }
else
printQuery(results, &my_popt, pset.queryFout, false, pset.logfile); fclose(fout);
/* close file/pipe, restore old setting */
setQFout(NULL);
pset.queryFout = queryFout_copy;
pset.queryFoutPipe = queryFoutPipe_copy;
} }
else else
printQuery(results, &my_popt, pset.queryFout, false, pset.logfile); printQuery(results, &my_popt, pset.queryFout, false, pset.logfile);
@ -1199,10 +1213,10 @@ ExecQueryUsingCursor(const char *query, double *elapsed_msec)
PGresult *results; PGresult *results;
PQExpBufferData buf; PQExpBufferData buf;
printQueryOpt my_popt = pset.popt; printQueryOpt my_popt = pset.popt;
FILE *queryFout_copy = pset.queryFout; FILE *fout;
bool queryFoutPipe_copy = pset.queryFoutPipe; bool is_pipe;
bool is_pager = false;
bool started_txn = false; bool started_txn = false;
bool did_pager = false;
int ntuples; int ntuples;
int fetch_count; int fetch_count;
char fetch_cmd[64]; char fetch_cmd[64];
@ -1268,21 +1282,22 @@ ExecQueryUsingCursor(const char *query, double *elapsed_msec)
/* prepare to write output to \g argument, if any */ /* prepare to write output to \g argument, if any */
if (pset.gfname) if (pset.gfname)
{ {
/* keep this code in sync with PrintQueryTuples */ if (!openQueryOutputFile(pset.gfname, &fout, &is_pipe))
pset.queryFout = stdout; /* so it doesn't get closed */
/* open file/pipe */
if (!setQFout(pset.gfname))
{ {
pset.queryFout = queryFout_copy;
pset.queryFoutPipe = queryFoutPipe_copy;
OK = false; OK = false;
goto cleanup; goto cleanup;
} }
if (is_pipe)
disable_sigpipe_trap();
}
else
{
fout = pset.queryFout;
is_pipe = false; /* doesn't matter */
} }
/* clear any pre-existing error indication on the output stream */ /* clear any pre-existing error indication on the output stream */
clearerr(pset.queryFout); clearerr(fout);
for (;;) for (;;)
{ {
@ -1302,12 +1317,10 @@ ExecQueryUsingCursor(const char *query, double *elapsed_msec)
if (PQresultStatus(results) != PGRES_TUPLES_OK) if (PQresultStatus(results) != PGRES_TUPLES_OK)
{ {
/* shut down pager before printing error message */ /* shut down pager before printing error message */
if (did_pager) if (is_pager)
{ {
ClosePager(pset.queryFout); ClosePager(fout);
pset.queryFout = queryFout_copy; is_pager = false;
pset.queryFoutPipe = queryFoutPipe_copy;
did_pager = false;
} }
OK = AcceptResult(results); OK = AcceptResult(results);
@ -1331,17 +1344,17 @@ ExecQueryUsingCursor(const char *query, double *elapsed_msec)
/* this is the last result set, so allow footer decoration */ /* this is the last result set, so allow footer decoration */
my_popt.topt.stop_table = true; my_popt.topt.stop_table = true;
} }
else if (pset.queryFout == stdout && !did_pager) else if (fout == stdout && !is_pager)
{ {
/* /*
* If query requires multiple result sets, hack to ensure that * If query requires multiple result sets, hack to ensure that
* only one pager instance is used for the whole mess * only one pager instance is used for the whole mess
*/ */
pset.queryFout = PageOutput(INT_MAX, &(my_popt.topt)); fout = PageOutput(INT_MAX, &(my_popt.topt));
did_pager = true; is_pager = true;
} }
printQuery(results, &my_popt, pset.queryFout, did_pager, pset.logfile); printQuery(results, &my_popt, fout, is_pager, pset.logfile);
PQclear(results); PQclear(results);
@ -1355,7 +1368,7 @@ ExecQueryUsingCursor(const char *query, double *elapsed_msec)
* the pager dies/exits/etc, there's no sense throwing more data at * the pager dies/exits/etc, there's no sense throwing more data at
* it. * it.
*/ */
flush_error = fflush(pset.queryFout); flush_error = fflush(fout);
/* /*
* Check if we are at the end, if a cancel was pressed, or if there * Check if we are at the end, if a cancel was pressed, or if there
@ -1365,24 +1378,25 @@ ExecQueryUsingCursor(const char *query, double *elapsed_msec)
* stop bothering to pull down more data. * stop bothering to pull down more data.
*/ */
if (ntuples < fetch_count || cancel_pressed || flush_error || if (ntuples < fetch_count || cancel_pressed || flush_error ||
ferror(pset.queryFout)) ferror(fout))
break; break;
} }
/* close \g argument file/pipe, restore old setting */
if (pset.gfname) if (pset.gfname)
{ {
/* keep this code in sync with PrintQueryTuples */ /* close \g argument file/pipe */
setQFout(NULL); if (is_pipe)
pset.queryFout = queryFout_copy;
pset.queryFoutPipe = queryFoutPipe_copy;
}
else if (did_pager)
{ {
ClosePager(pset.queryFout); pclose(fout);
pset.queryFout = queryFout_copy; restore_sigpipe_trap();
pset.queryFoutPipe = queryFoutPipe_copy; }
else
fclose(fout);
}
else if (is_pager)
{
/* close transient pager */
ClosePager(fout);
} }
cleanup: cleanup:

View File

@ -16,6 +16,7 @@
#define atooid(x) ((Oid) strtoul((x), NULL, 10)) #define atooid(x) ((Oid) strtoul((x), NULL, 10))
extern bool openQueryOutputFile(const char *fname, FILE **fout, bool *is_pipe);
extern bool setQFout(const char *fname); extern bool setQFout(const char *fname);
extern void psql_error(const char *fmt,...) pg_attribute_printf(1, 2); extern void psql_error(const char *fmt,...) pg_attribute_printf(1, 2);

View File

@ -312,9 +312,7 @@ do_copy(const char *args)
fflush(stdout); fflush(stdout);
fflush(stderr); fflush(stderr);
errno = 0; errno = 0;
#ifndef WIN32 disable_sigpipe_trap();
pqsignal(SIGPIPE, SIG_IGN);
#endif
copystream = popen(options->file, PG_BINARY_W); copystream = popen(options->file, PG_BINARY_W);
} }
else else
@ -399,9 +397,7 @@ do_copy(const char *args)
} }
success = false; success = false;
} }
#ifndef WIN32 restore_sigpipe_trap();
pqsignal(SIGPIPE, SIG_DFL);
#endif
} }
else else
{ {

View File

@ -39,6 +39,13 @@
*/ */
volatile bool cancel_pressed = false; volatile bool cancel_pressed = false;
/*
* Likewise, the sigpipe_trap and pager open/close functions are here rather
* than in common.c so that this file can be used by non-psql programs.
*/
static bool always_ignore_sigpipe = false;
/* info for locale-aware numeric formatting; set up by setDecimalLocale() */ /* info for locale-aware numeric formatting; set up by setDecimalLocale() */
static char *decimal_point; static char *decimal_point;
static int groupdigits; static int groupdigits;
@ -2779,6 +2786,57 @@ print_troff_ms_vertical(const printTableContent *cont, FILE *fout)
/********************************/ /********************************/
/*
* disable_sigpipe_trap
*
* Turn off SIGPIPE interrupt --- call this before writing to a temporary
* query output file that is a pipe.
*
* No-op on Windows, where there's no SIGPIPE interrupts.
*/
void
disable_sigpipe_trap(void)
{
#ifndef WIN32
pqsignal(SIGPIPE, SIG_IGN);
#endif
}
/*
* restore_sigpipe_trap
*
* Restore normal SIGPIPE interrupt --- call this when done writing to a
* temporary query output file that was (or might have been) a pipe.
*
* Note: within psql, we enable SIGPIPE interrupts unless the permanent query
* output file is a pipe, in which case they should be kept off. This
* approach works only because psql is not currently complicated enough to
* have nested usages of short-lived output files. Otherwise we'd probably
* need a genuine save-and-restore-state approach; but for now, that would be
* useless complication. In non-psql programs, this always enables SIGPIPE.
*
* No-op on Windows, where there's no SIGPIPE interrupts.
*/
void
restore_sigpipe_trap(void)
{
#ifndef WIN32
pqsignal(SIGPIPE, always_ignore_sigpipe ? SIG_IGN : SIG_DFL);
#endif
}
/*
* set_sigpipe_trap_state
*
* Set the trap state that restore_sigpipe_trap should restore to.
*/
void
set_sigpipe_trap_state(bool ignore)
{
always_ignore_sigpipe = ignore;
}
/* /*
* PageOutput * PageOutput
* *
@ -2792,9 +2850,6 @@ PageOutput(int lines, const printTableOpt *topt)
/* check whether we need / can / are supposed to use pager */ /* check whether we need / can / are supposed to use pager */
if (topt && topt->pager && isatty(fileno(stdin)) && isatty(fileno(stdout))) if (topt && topt->pager && isatty(fileno(stdin)) && isatty(fileno(stdout)))
{ {
const char *pagerprog;
FILE *pagerpipe;
#ifdef TIOCGWINSZ #ifdef TIOCGWINSZ
unsigned short int pager = topt->pager; unsigned short int pager = topt->pager;
int min_lines = topt->pager_min_lines; int min_lines = topt->pager_min_lines;
@ -2807,20 +2862,19 @@ PageOutput(int lines, const printTableOpt *topt)
if (result == -1 if (result == -1
|| (lines >= screen_size.ws_row && lines >= min_lines) || (lines >= screen_size.ws_row && lines >= min_lines)
|| pager > 1) || pager > 1)
{
#endif #endif
{
const char *pagerprog;
FILE *pagerpipe;
pagerprog = getenv("PAGER"); pagerprog = getenv("PAGER");
if (!pagerprog) if (!pagerprog)
pagerprog = DEFAULT_PAGER; pagerprog = DEFAULT_PAGER;
#ifndef WIN32 disable_sigpipe_trap();
pqsignal(SIGPIPE, SIG_IGN);
#endif
pagerpipe = popen(pagerprog, "w"); pagerpipe = popen(pagerprog, "w");
if (pagerpipe) if (pagerpipe)
return pagerpipe; return pagerpipe;
#ifdef TIOCGWINSZ
} }
#endif
} }
return stdout; return stdout;
@ -2848,9 +2902,7 @@ ClosePager(FILE *pagerpipe)
fprintf(pagerpipe, _("Interrupted\n")); fprintf(pagerpipe, _("Interrupted\n"));
pclose(pagerpipe); pclose(pagerpipe);
#ifndef WIN32 restore_sigpipe_trap();
pqsignal(SIGPIPE, SIG_DFL);
#endif
} }
} }

View File

@ -167,6 +167,10 @@ extern const printTextFormat pg_asciiformat_old;
extern const printTextFormat pg_utf8format; extern const printTextFormat pg_utf8format;
extern void disable_sigpipe_trap(void);
extern void restore_sigpipe_trap(void);
extern void set_sigpipe_trap_state(bool ignore);
extern FILE *PageOutput(int lines, const printTableOpt *topt); extern FILE *PageOutput(int lines, const printTableOpt *topt);
extern void ClosePager(FILE *pagerpipe); extern void ClosePager(FILE *pagerpipe);

View File

@ -461,7 +461,8 @@ parse_psql_options(int argc, char *argv[], struct adhoc_opts * options)
options->no_readline = true; options->no_readline = true;
break; break;
case 'o': case 'o':
setQFout(optarg); if (!setQFout(optarg))
exit(EXIT_FAILURE);
break; break;
case 'p': case 'p':
options->port = pg_strdup(optarg); options->port = pg_strdup(optarg);