diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index 8eca4cf23b..6e8c62395c 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -1531,6 +1531,7 @@ exec_command(const char *cmd, if (fname[0] == '|') { is_pipe = true; + disable_sigpipe_trap(); fd = popen(&fname[1], "w"); } else @@ -1565,6 +1566,9 @@ exec_command(const char *cmd, } } + if (is_pipe) + restore_sigpipe_trap(); + free(fname); } diff --git a/src/bin/psql/common.c b/src/bin/psql/common.c index 3254a140b3..a287eeee19 100644 --- a/src/bin/psql/common.c +++ b/src/bin/psql/common.c @@ -26,23 +26,66 @@ #include "mbprint.h" - static bool ExecQueryUsingCursor(const char *query, double *elapsed_msec); static bool command_no_begin(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 * -- handler for -o command line option and \o command * - * Tries to open file fname (or pipe if fname starts with '|') - * and stores the file handle in pset) - * Upon failure, sets stdout and returns false. + * On success, updates pset with the new output file and returns true. + * On failure, returns false without changing pset state. */ bool 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 */ if (pset.queryFout && pset.queryFout != stdout && pset.queryFout != stderr) @@ -53,45 +96,20 @@ setQFout(const char *fname) fclose(pset.queryFout); } - /* If no filename, set stdout */ - if (!fname || fname[0] == '\0') - { - 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; - } + pset.queryFout = fout; + pset.queryFoutPipe = is_pipe; - if (!(pset.queryFout)) - { - psql_error("%s: %s\n", fname, strerror(errno)); - pset.queryFout = stdout; - pset.queryFoutPipe = false; - status = false; - } + /* Adjust SIGPIPE handling appropriately: ignore signal if is_pipe */ + set_sigpipe_trap_state(is_pipe); + restore_sigpipe_trap(); - /* Direct signals */ -#ifndef WIN32 - pqsignal(SIGPIPE, pset.queryFoutPipe ? SIG_IGN : SIG_DFL); -#endif - - return status; + return true; } - /* * Error reporting for scripts. Errors should look like * psql:filename:lineno: message - * */ void psql_error(const char *fmt,...) @@ -611,27 +629,23 @@ PrintQueryTuples(const PGresult *results) /* write output to \g argument, if any */ if (pset.gfname) { - /* keep this code in sync with ExecQueryUsingCursor */ - FILE *queryFout_copy = pset.queryFout; - bool queryFoutPipe_copy = pset.queryFoutPipe; + FILE *fout; + bool 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; + if (!openQueryOutputFile(pset.gfname, &fout, &is_pipe)) return false; + if (is_pipe) + disable_sigpipe_trap(); + + printQuery(results, &my_popt, fout, false, pset.logfile); + + if (is_pipe) + { + pclose(fout); + restore_sigpipe_trap(); } - - printQuery(results, &my_popt, pset.queryFout, false, pset.logfile); - - /* close file/pipe, restore old setting */ - setQFout(NULL); - - pset.queryFout = queryFout_copy; - pset.queryFoutPipe = queryFoutPipe_copy; + else + fclose(fout); } else printQuery(results, &my_popt, pset.queryFout, false, pset.logfile); @@ -1199,10 +1213,10 @@ ExecQueryUsingCursor(const char *query, double *elapsed_msec) PGresult *results; PQExpBufferData buf; printQueryOpt my_popt = pset.popt; - FILE *queryFout_copy = pset.queryFout; - bool queryFoutPipe_copy = pset.queryFoutPipe; + FILE *fout; + bool is_pipe; + bool is_pager = false; bool started_txn = false; - bool did_pager = false; int ntuples; int fetch_count; char fetch_cmd[64]; @@ -1268,21 +1282,22 @@ ExecQueryUsingCursor(const char *query, double *elapsed_msec) /* prepare to write output to \g argument, if any */ if (pset.gfname) { - /* keep this code in sync with PrintQueryTuples */ - pset.queryFout = stdout; /* so it doesn't get closed */ - - /* open file/pipe */ - if (!setQFout(pset.gfname)) + if (!openQueryOutputFile(pset.gfname, &fout, &is_pipe)) { - pset.queryFout = queryFout_copy; - pset.queryFoutPipe = queryFoutPipe_copy; OK = false; 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 */ - clearerr(pset.queryFout); + clearerr(fout); for (;;) { @@ -1302,12 +1317,10 @@ ExecQueryUsingCursor(const char *query, double *elapsed_msec) if (PQresultStatus(results) != PGRES_TUPLES_OK) { /* shut down pager before printing error message */ - if (did_pager) + if (is_pager) { - ClosePager(pset.queryFout); - pset.queryFout = queryFout_copy; - pset.queryFoutPipe = queryFoutPipe_copy; - did_pager = false; + ClosePager(fout); + is_pager = false; } OK = AcceptResult(results); @@ -1331,17 +1344,17 @@ ExecQueryUsingCursor(const char *query, double *elapsed_msec) /* this is the last result set, so allow footer decoration */ 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 * only one pager instance is used for the whole mess */ - pset.queryFout = PageOutput(INT_MAX, &(my_popt.topt)); - did_pager = true; + fout = PageOutput(INT_MAX, &(my_popt.topt)); + is_pager = true; } - printQuery(results, &my_popt, pset.queryFout, did_pager, pset.logfile); + printQuery(results, &my_popt, fout, is_pager, pset.logfile); 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 * 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 @@ -1365,24 +1378,25 @@ ExecQueryUsingCursor(const char *query, double *elapsed_msec) * stop bothering to pull down more data. */ if (ntuples < fetch_count || cancel_pressed || flush_error || - ferror(pset.queryFout)) + ferror(fout)) break; } - /* close \g argument file/pipe, restore old setting */ if (pset.gfname) { - /* keep this code in sync with PrintQueryTuples */ - setQFout(NULL); - - pset.queryFout = queryFout_copy; - pset.queryFoutPipe = queryFoutPipe_copy; + /* close \g argument file/pipe */ + if (is_pipe) + { + pclose(fout); + restore_sigpipe_trap(); + } + else + fclose(fout); } - else if (did_pager) + else if (is_pager) { - ClosePager(pset.queryFout); - pset.queryFout = queryFout_copy; - pset.queryFoutPipe = queryFoutPipe_copy; + /* close transient pager */ + ClosePager(fout); } cleanup: diff --git a/src/bin/psql/common.h b/src/bin/psql/common.h index caf31d19b8..62a602632a 100644 --- a/src/bin/psql/common.h +++ b/src/bin/psql/common.h @@ -16,6 +16,7 @@ #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 void psql_error(const char *fmt,...) pg_attribute_printf(1, 2); diff --git a/src/bin/psql/copy.c b/src/bin/psql/copy.c index c0fc28373a..a09408bf55 100644 --- a/src/bin/psql/copy.c +++ b/src/bin/psql/copy.c @@ -37,7 +37,7 @@ * where 'filename' can be one of the following: * '' | PROGRAM '' | stdin | stdout | pstdout | pstdout * and 'query' can be one of the following: - * SELECT | UPDATE | INSERT | DELETE + * SELECT | UPDATE | INSERT | DELETE * * An undocumented fact is that you can still write BINARY before the * tablename; this is a hangover from the pre-7.3 syntax. The options @@ -312,9 +312,7 @@ do_copy(const char *args) fflush(stdout); fflush(stderr); errno = 0; -#ifndef WIN32 - pqsignal(SIGPIPE, SIG_IGN); -#endif + disable_sigpipe_trap(); copystream = popen(options->file, PG_BINARY_W); } else @@ -399,9 +397,7 @@ do_copy(const char *args) } success = false; } -#ifndef WIN32 - pqsignal(SIGPIPE, SIG_DFL); -#endif + restore_sigpipe_trap(); } else { diff --git a/src/bin/psql/print.c b/src/bin/psql/print.c index 190f2bc5d8..05d4b3162c 100644 --- a/src/bin/psql/print.c +++ b/src/bin/psql/print.c @@ -39,6 +39,13 @@ */ 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() */ static char *decimal_point; static int groupdigits; @@ -2775,10 +2782,61 @@ print_troff_ms_vertical(const printTableContent *cont, FILE *fout) /********************************/ -/* Public functions */ +/* Public functions */ /********************************/ +/* + * 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 * @@ -2792,9 +2850,6 @@ PageOutput(int lines, const printTableOpt *topt) /* check whether we need / can / are supposed to use pager */ if (topt && topt->pager && isatty(fileno(stdin)) && isatty(fileno(stdout))) { - const char *pagerprog; - FILE *pagerpipe; - #ifdef TIOCGWINSZ unsigned short int pager = topt->pager; int min_lines = topt->pager_min_lines; @@ -2807,20 +2862,19 @@ PageOutput(int lines, const printTableOpt *topt) if (result == -1 || (lines >= screen_size.ws_row && lines >= min_lines) || pager > 1) - { #endif + { + const char *pagerprog; + FILE *pagerpipe; + pagerprog = getenv("PAGER"); if (!pagerprog) pagerprog = DEFAULT_PAGER; -#ifndef WIN32 - pqsignal(SIGPIPE, SIG_IGN); -#endif + disable_sigpipe_trap(); pagerpipe = popen(pagerprog, "w"); if (pagerpipe) return pagerpipe; -#ifdef TIOCGWINSZ } -#endif } return stdout; @@ -2848,9 +2902,7 @@ ClosePager(FILE *pagerpipe) fprintf(pagerpipe, _("Interrupted\n")); pclose(pagerpipe); -#ifndef WIN32 - pqsignal(SIGPIPE, SIG_DFL); -#endif + restore_sigpipe_trap(); } } diff --git a/src/bin/psql/print.h b/src/bin/psql/print.h index df514cffb0..fd56598426 100644 --- a/src/bin/psql/print.h +++ b/src/bin/psql/print.h @@ -167,6 +167,10 @@ extern const printTextFormat pg_asciiformat_old; 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 void ClosePager(FILE *pagerpipe); diff --git a/src/bin/psql/startup.c b/src/bin/psql/startup.c index 7aa997d479..4e5021a43d 100644 --- a/src/bin/psql/startup.c +++ b/src/bin/psql/startup.c @@ -461,7 +461,8 @@ parse_psql_options(int argc, char *argv[], struct adhoc_opts * options) options->no_readline = true; break; case 'o': - setQFout(optarg); + if (!setQFout(optarg)) + exit(EXIT_FAILURE); break; case 'p': options->port = pg_strdup(optarg);