Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit 344cdff

Browse files
committed
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.
1 parent f15b820 commit 344cdff

File tree

7 files changed

+178
-106
lines changed

7 files changed

+178
-106
lines changed

src/bin/psql/command.c

+4
Original file line numberDiff line numberDiff line change
@@ -1531,6 +1531,7 @@ exec_command(const char *cmd,
15311531
if (fname[0] == '|')
15321532
{
15331533
is_pipe = true;
1534+
disable_sigpipe_trap();
15341535
fd = popen(&fname[1], "w");
15351536
}
15361537
else
@@ -1565,6 +1566,9 @@ exec_command(const char *cmd,
15651566
}
15661567
}
15671568

1569+
if (is_pipe)
1570+
restore_sigpipe_trap();
1571+
15681572
free(fname);
15691573
}
15701574

src/bin/psql/common.c

+99-85
Original file line numberDiff line numberDiff line change
@@ -26,72 +26,90 @@
2626
#include "mbprint.h"
2727

2828

29-
3029
static bool ExecQueryUsingCursor(const char *query, double *elapsed_msec);
3130
static bool command_no_begin(const char *query);
3231
static bool is_select_command(const char *query);
3332

33+
3434
/*
35-
* setQFout
36-
* -- handler for -o command line option and \o command
35+
* openQueryOutputFile --- attempt to open a query output file
3736
*
38-
* Tries to open file fname (or pipe if fname starts with '|')
39-
* and stores the file handle in pset)
40-
* Upon failure, sets stdout and returns false.
37+
* fname == NULL selects stdout, else an initial '|' selects a pipe,
38+
* else plain file.
39+
*
40+
* Returns output file pointer into *fout, and is-a-pipe flag into *is_pipe.
41+
* Caller is responsible for adjusting SIGPIPE state if it's a pipe.
42+
*
43+
* On error, reports suitable error message and returns FALSE.
4144
*/
4245
bool
43-
setQFout(const char *fname)
46+
openQueryOutputFile(const char *fname, FILE **fout, bool *is_pipe)
4447
{
45-
bool status = true;
46-
47-
/* Close old file/pipe */
48-
if (pset.queryFout && pset.queryFout != stdout && pset.queryFout != stderr)
49-
{
50-
if (pset.queryFoutPipe)
51-
pclose(pset.queryFout);
52-
else
53-
fclose(pset.queryFout);
54-
}
55-
56-
/* If no filename, set stdout */
5748
if (!fname || fname[0] == '\0')
5849
{
59-
pset.queryFout = stdout;
60-
pset.queryFoutPipe = false;
50+
*fout = stdout;
51+
*is_pipe = false;
6152
}
6253
else if (*fname == '|')
6354
{
64-
pset.queryFout = popen(fname + 1, "w");
65-
pset.queryFoutPipe = true;
55+
*fout = popen(fname + 1, "w");
56+
*is_pipe = true;
6657
}
6758
else
6859
{
69-
pset.queryFout = fopen(fname, "w");
70-
pset.queryFoutPipe = false;
60+
*fout = fopen(fname, "w");
61+
*is_pipe = false;
7162
}
7263

73-
if (!(pset.queryFout))
64+
if (*fout == NULL)
7465
{
7566
psql_error("%s: %s\n", fname, strerror(errno));
76-
pset.queryFout = stdout;
77-
pset.queryFoutPipe = false;
78-
status = false;
67+
return false;
7968
}
8069

81-
/* Direct signals */
82-
#ifndef WIN32
83-
pqsignal(SIGPIPE, pset.queryFoutPipe ? SIG_IGN : SIG_DFL);
84-
#endif
85-
86-
return status;
70+
return true;
8771
}
8872

73+
/*
74+
* setQFout
75+
* -- handler for -o command line option and \o command
76+
*
77+
* On success, updates pset with the new output file and returns true.
78+
* On failure, returns false without changing pset state.
79+
*/
80+
bool
81+
setQFout(const char *fname)
82+
{
83+
FILE *fout;
84+
bool is_pipe;
85+
86+
/* First make sure we can open the new output file/pipe */
87+
if (!openQueryOutputFile(fname, &fout, &is_pipe))
88+
return false;
89+
90+
/* Close old file/pipe */
91+
if (pset.queryFout && pset.queryFout != stdout && pset.queryFout != stderr)
92+
{
93+
if (pset.queryFoutPipe)
94+
pclose(pset.queryFout);
95+
else
96+
fclose(pset.queryFout);
97+
}
98+
99+
pset.queryFout = fout;
100+
pset.queryFoutPipe = is_pipe;
101+
102+
/* Adjust SIGPIPE handling appropriately: ignore signal if is_pipe */
103+
set_sigpipe_trap_state(is_pipe);
104+
restore_sigpipe_trap();
105+
106+
return true;
107+
}
89108

90109

91110
/*
92111
* Error reporting for scripts. Errors should look like
93112
* psql:filename:lineno: message
94-
*
95113
*/
96114
void
97115
psql_error(const char *fmt,...)
@@ -611,27 +629,23 @@ PrintQueryTuples(const PGresult *results)
611629
/* write output to \g argument, if any */
612630
if (pset.gfname)
613631
{
614-
/* keep this code in sync with ExecQueryUsingCursor */
615-
FILE *queryFout_copy = pset.queryFout;
616-
bool queryFoutPipe_copy = pset.queryFoutPipe;
617-
618-
pset.queryFout = stdout; /* so it doesn't get closed */
632+
FILE *fout;
633+
bool is_pipe;
619634

620-
/* open file/pipe */
621-
if (!setQFout(pset.gfname))
622-
{
623-
pset.queryFout = queryFout_copy;
624-
pset.queryFoutPipe = queryFoutPipe_copy;
635+
if (!openQueryOutputFile(pset.gfname, &fout, &is_pipe))
625636
return false;
626-
}
627-
628-
printQuery(results, &my_popt, pset.queryFout, false, pset.logfile);
637+
if (is_pipe)
638+
disable_sigpipe_trap();
629639

630-
/* close file/pipe, restore old setting */
631-
setQFout(NULL);
640+
printQuery(results, &my_popt, fout, false, pset.logfile);
632641

633-
pset.queryFout = queryFout_copy;
634-
pset.queryFoutPipe = queryFoutPipe_copy;
642+
if (is_pipe)
643+
{
644+
pclose(fout);
645+
restore_sigpipe_trap();
646+
}
647+
else
648+
fclose(fout);
635649
}
636650
else
637651
printQuery(results, &my_popt, pset.queryFout, false, pset.logfile);
@@ -1199,10 +1213,10 @@ ExecQueryUsingCursor(const char *query, double *elapsed_msec)
11991213
PGresult *results;
12001214
PQExpBufferData buf;
12011215
printQueryOpt my_popt = pset.popt;
1202-
FILE *queryFout_copy = pset.queryFout;
1203-
bool queryFoutPipe_copy = pset.queryFoutPipe;
1216+
FILE *fout;
1217+
bool is_pipe;
1218+
bool is_pager = false;
12041219
bool started_txn = false;
1205-
bool did_pager = false;
12061220
int ntuples;
12071221
int fetch_count;
12081222
char fetch_cmd[64];
@@ -1268,21 +1282,22 @@ ExecQueryUsingCursor(const char *query, double *elapsed_msec)
12681282
/* prepare to write output to \g argument, if any */
12691283
if (pset.gfname)
12701284
{
1271-
/* keep this code in sync with PrintQueryTuples */
1272-
pset.queryFout = stdout; /* so it doesn't get closed */
1273-
1274-
/* open file/pipe */
1275-
if (!setQFout(pset.gfname))
1285+
if (!openQueryOutputFile(pset.gfname, &fout, &is_pipe))
12761286
{
1277-
pset.queryFout = queryFout_copy;
1278-
pset.queryFoutPipe = queryFoutPipe_copy;
12791287
OK = false;
12801288
goto cleanup;
12811289
}
1290+
if (is_pipe)
1291+
disable_sigpipe_trap();
1292+
}
1293+
else
1294+
{
1295+
fout = pset.queryFout;
1296+
is_pipe = false; /* doesn't matter */
12821297
}
12831298

12841299
/* clear any pre-existing error indication on the output stream */
1285-
clearerr(pset.queryFout);
1300+
clearerr(fout);
12861301

12871302
for (;;)
12881303
{
@@ -1302,12 +1317,10 @@ ExecQueryUsingCursor(const char *query, double *elapsed_msec)
13021317
if (PQresultStatus(results) != PGRES_TUPLES_OK)
13031318
{
13041319
/* shut down pager before printing error message */
1305-
if (did_pager)
1320+
if (is_pager)
13061321
{
1307-
ClosePager(pset.queryFout);
1308-
pset.queryFout = queryFout_copy;
1309-
pset.queryFoutPipe = queryFoutPipe_copy;
1310-
did_pager = false;
1322+
ClosePager(fout);
1323+
is_pager = false;
13111324
}
13121325

13131326
OK = AcceptResult(results);
@@ -1331,17 +1344,17 @@ ExecQueryUsingCursor(const char *query, double *elapsed_msec)
13311344
/* this is the last result set, so allow footer decoration */
13321345
my_popt.topt.stop_table = true;
13331346
}
1334-
else if (pset.queryFout == stdout && !did_pager)
1347+
else if (fout == stdout && !is_pager)
13351348
{
13361349
/*
13371350
* If query requires multiple result sets, hack to ensure that
13381351
* only one pager instance is used for the whole mess
13391352
*/
1340-
pset.queryFout = PageOutput(INT_MAX, &(my_popt.topt));
1341-
did_pager = true;
1353+
fout = PageOutput(INT_MAX, &(my_popt.topt));
1354+
is_pager = true;
13421355
}
13431356

1344-
printQuery(results, &my_popt, pset.queryFout, did_pager, pset.logfile);
1357+
printQuery(results, &my_popt, fout, is_pager, pset.logfile);
13451358

13461359
PQclear(results);
13471360

@@ -1355,7 +1368,7 @@ ExecQueryUsingCursor(const char *query, double *elapsed_msec)
13551368
* the pager dies/exits/etc, there's no sense throwing more data at
13561369
* it.
13571370
*/
1358-
flush_error = fflush(pset.queryFout);
1371+
flush_error = fflush(fout);
13591372

13601373
/*
13611374
* 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)
13651378
* stop bothering to pull down more data.
13661379
*/
13671380
if (ntuples < fetch_count || cancel_pressed || flush_error ||
1368-
ferror(pset.queryFout))
1381+
ferror(fout))
13691382
break;
13701383
}
13711384

1372-
/* close \g argument file/pipe, restore old setting */
13731385
if (pset.gfname)
13741386
{
1375-
/* keep this code in sync with PrintQueryTuples */
1376-
setQFout(NULL);
1377-
1378-
pset.queryFout = queryFout_copy;
1379-
pset.queryFoutPipe = queryFoutPipe_copy;
1387+
/* close \g argument file/pipe */
1388+
if (is_pipe)
1389+
{
1390+
pclose(fout);
1391+
restore_sigpipe_trap();
1392+
}
1393+
else
1394+
fclose(fout);
13801395
}
1381-
else if (did_pager)
1396+
else if (is_pager)
13821397
{
1383-
ClosePager(pset.queryFout);
1384-
pset.queryFout = queryFout_copy;
1385-
pset.queryFoutPipe = queryFoutPipe_copy;
1398+
/* close transient pager */
1399+
ClosePager(fout);
13861400
}
13871401

13881402
cleanup:

src/bin/psql/common.h

+1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
#define atooid(x) ((Oid) strtoul((x), NULL, 10))
1818

19+
extern bool openQueryOutputFile(const char *fname, FILE **fout, bool *is_pipe);
1920
extern bool setQFout(const char *fname);
2021

2122
extern void psql_error(const char *fmt,...) pg_attribute_printf(1, 2);

src/bin/psql/copy.c

+3-7
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
* where 'filename' can be one of the following:
3838
* '<file path>' | PROGRAM '<command>' | stdin | stdout | pstdout | pstdout
3939
* and 'query' can be one of the following:
40-
* SELECT | UPDATE | INSERT | DELETE
40+
* SELECT | UPDATE | INSERT | DELETE
4141
*
4242
* An undocumented fact is that you can still write BINARY before the
4343
* tablename; this is a hangover from the pre-7.3 syntax. The options
@@ -312,9 +312,7 @@ do_copy(const char *args)
312312
fflush(stdout);
313313
fflush(stderr);
314314
errno = 0;
315-
#ifndef WIN32
316-
pqsignal(SIGPIPE, SIG_IGN);
317-
#endif
315+
disable_sigpipe_trap();
318316
copystream = popen(options->file, PG_BINARY_W);
319317
}
320318
else
@@ -399,9 +397,7 @@ do_copy(const char *args)
399397
}
400398
success = false;
401399
}
402-
#ifndef WIN32
403-
pqsignal(SIGPIPE, SIG_DFL);
404-
#endif
400+
restore_sigpipe_trap();
405401
}
406402
else
407403
{

0 commit comments

Comments
 (0)