I wrote: > I just had a thought about that: suppose we add a flag to CopyState > to indicate whether we reached EOF while reading. ... > Then the logic in ClosePipeToProgram could be "if we did not reach > EOF, then a SIGPIPE termination is unsurprising. If we did reach > EOF, then SIGPIPE is an error." If the called program gets SIGPIPE > for some reason other than us closing the pipe early, then we would > see EOF next time we try to read, and correctly report that there's > a problem.
Concretely, something like the attached. I'm not quite sure whether the reached_eof test should be "if (bytesread == 0)" or "if (bytesread < maxread)". The former seems more certain to indicate real EOF; are there other cases where the fread might return a short read? On the other hand, if we support in-band EOF indicators (\. for instance) then we might stop without having made an extra call to CopyGetData that would see bytesread == 0. On the third hand, if we do do that it's not quite clear to me whether SIGPIPE is ignorable or not. regards, tom lane
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c index 6588ebd..8975446 100644 *** a/src/backend/commands/copy.c --- b/src/backend/commands/copy.c *************** typedef struct CopyStateData *** 114,120 **** FILE *copy_file; /* used if copy_dest == COPY_FILE */ StringInfo fe_msgbuf; /* used for all dests during COPY TO, only for * dest == COPY_NEW_FE in COPY FROM */ ! bool fe_eof; /* true if detected end of copy data */ EolType eol_type; /* EOL type of input */ int file_encoding; /* file or remote side's character encoding */ bool need_transcoding; /* file encoding diff from server? */ --- 114,122 ---- FILE *copy_file; /* used if copy_dest == COPY_FILE */ StringInfo fe_msgbuf; /* used for all dests during COPY TO, only for * dest == COPY_NEW_FE in COPY FROM */ ! bool is_copy_from; /* COPY TO, or COPY FROM? */ ! bool reached_eof; /* true if we read to end of copy data (not ! * all copy_dest types maintain this) */ EolType eol_type; /* EOL type of input */ int file_encoding; /* file or remote side's character encoding */ bool need_transcoding; /* file encoding diff from server? */ *************** CopyGetData(CopyState cstate, void *data *** 575,580 **** --- 577,584 ---- ereport(ERROR, (errcode_for_file_access(), errmsg("could not read from COPY file: %m"))); + if (bytesread == 0) + cstate->reached_eof = true; break; case COPY_OLD_FE: *************** CopyGetData(CopyState cstate, void *data *** 595,601 **** bytesread = minread; break; case COPY_NEW_FE: ! while (maxread > 0 && bytesread < minread && !cstate->fe_eof) { int avail; --- 599,605 ---- bytesread = minread; break; case COPY_NEW_FE: ! while (maxread > 0 && bytesread < minread && !cstate->reached_eof) { int avail; *************** CopyGetData(CopyState cstate, void *data *** 623,629 **** break; case 'c': /* CopyDone */ /* COPY IN correctly terminated by frontend */ ! cstate->fe_eof = true; return bytesread; case 'f': /* CopyFail */ ereport(ERROR, --- 627,633 ---- break; case 'c': /* CopyDone */ /* COPY IN correctly terminated by frontend */ ! cstate->reached_eof = true; return bytesread; case 'f': /* CopyFail */ ereport(ERROR, *************** ProcessCopyOptions(ParseState *pstate, *** 1050,1055 **** --- 1054,1061 ---- if (cstate == NULL) cstate = (CopyStateData *) palloc0(sizeof(CopyStateData)); + cstate->is_copy_from = is_from; + cstate->file_encoding = -1; /* Extract options from the statement node tree */ *************** ClosePipeToProgram(CopyState cstate) *** 1727,1737 **** --- 1733,1755 ---- (errcode_for_file_access(), errmsg("could not close pipe to external command: %m"))); else if (pclose_rc != 0) + { + /* + * If we ended a COPY FROM PROGRAM before reaching EOF, then it's + * expectable for the called program to fail with SIGPIPE, and we + * should not report that as an error. Otherwise, SIGPIPE indicates a + * problem. + */ + if (cstate->is_copy_from && !cstate->reached_eof && + WIFSIGNALED(pclose_rc) && WTERMSIG(pclose_rc) == SIGPIPE) + return; + ereport(ERROR, (errcode(ERRCODE_EXTERNAL_ROUTINE_EXCEPTION), errmsg("program \"%s\" failed", cstate->filename), errdetail_internal("%s", wait_result_to_str(pclose_rc)))); + } } /* *************** BeginCopyFrom(ParseState *pstate, *** 3169,3175 **** oldcontext = MemoryContextSwitchTo(cstate->copycontext); /* Initialize state variables */ ! cstate->fe_eof = false; cstate->eol_type = EOL_UNKNOWN; cstate->cur_relname = RelationGetRelationName(cstate->rel); cstate->cur_lineno = 0; --- 3187,3193 ---- oldcontext = MemoryContextSwitchTo(cstate->copycontext); /* Initialize state variables */ ! cstate->reached_eof = false; cstate->eol_type = EOL_UNKNOWN; cstate->cur_relname = RelationGetRelationName(cstate->rel); cstate->cur_lineno = 0; diff --git a/src/backend/storage/file/fd.c b/src/backend/storage/file/fd.c index 2d75773..6bc0244 100644 *** a/src/backend/storage/file/fd.c --- b/src/backend/storage/file/fd.c *************** OpenTransientFilePerm(const char *fileNa *** 2271,2281 **** --- 2271,2287 ---- * Routines that want to initiate a pipe stream should use OpenPipeStream * rather than plain popen(). This lets fd.c deal with freeing FDs if * necessary. When done, call ClosePipeStream rather than pclose. + * + * This function also ensures that the popen'd program is run with default + * SIGPIPE and SIGUSR2 processing, rather than the SIG_IGN settings the + * backend normally uses. This ensures desirable response to, eg, closing + * a read pipe early. */ FILE * OpenPipeStream(const char *command, const char *mode) { FILE *file; + int save_errno; DO_DB(elog(LOG, "OpenPipeStream: Allocated %d (%s)", numAllocatedDescs, command)); *************** OpenPipeStream(const char *command, cons *** 2293,2300 **** TryAgain: fflush(stdout); fflush(stderr); errno = 0; ! if ((file = popen(command, mode)) != NULL) { AllocateDesc *desc = &allocatedDescs[numAllocatedDescs]; --- 2299,2313 ---- TryAgain: fflush(stdout); fflush(stderr); + pqsignal(SIGPIPE, SIG_DFL); + pqsignal(SIGUSR2, SIG_DFL); errno = 0; ! file = popen(command, mode); ! save_errno = errno; ! pqsignal(SIGPIPE, SIG_IGN); ! pqsignal(SIGUSR2, SIG_IGN); ! errno = save_errno; ! if (file != NULL) { AllocateDesc *desc = &allocatedDescs[numAllocatedDescs]; *************** TryAgain: *** 2307,2314 **** if (errno == EMFILE || errno == ENFILE) { - int save_errno = errno; - ereport(LOG, (errcode(ERRCODE_INSUFFICIENT_RESOURCES), errmsg("out of file descriptors: %m; release and retry"))); --- 2320,2325 ---- diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c index a3b9757..cfa416a 100644 *** a/src/backend/tcop/postgres.c --- b/src/backend/tcop/postgres.c *************** PostgresMain(int argc, char *argv[], *** 3769,3774 **** --- 3769,3778 ---- * handler in the postmaster to reserve the signal. (Of course, this isn't * an issue for signals that are locally generated, such as SIGALRM and * SIGPIPE.) + * + * Also note: signals that are set to SIG_IGN here should be reset in + * OpenPipeStream, so that exec'd programs see a standard signal + * environment. */ if (am_walsender) WalSndSignals();