From: Pádraig Brady Date: Tue, 28 Oct 2025 19:30:08 +0000 (+0000) Subject: sort: fix silent exit upon SIGPIPE from --compress-program X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=b294aff3fe6c8ebeda02b8c77877ba618474de41;p=thirdparty%2Fcoreutils.git sort: fix silent exit upon SIGPIPE from --compress-program * src/sort.c (main): Ignore SIGPIPE so we've more control over how we handle for stdout and compression programs. (sort_die): Handle EPIPE from stdout and mimic a standard SIGPIPE, otherwise reverting to a standard exit(SORT_FAILURE); * tests/sort/sort-compress-proc.sh: Add a test case. * NEWS: Mention the bug fix. --- diff --git a/NEWS b/NEWS index 96b54e108b..0cc760aeed 100644 --- a/NEWS +++ b/NEWS @@ -28,6 +28,10 @@ GNU coreutils NEWS -*- outline -*- Although these directories are nonempty, 'rmdir DIR' succeeds on them. [bug introduced in coreutils-8.16] + 'sort --compress-program' now diagnoses if it can't write more data to an + exited compressor. Previously sort could have exited silently in this case. + [bug introduced in coreutils-6.8] + 'tail' outputs the correct number of lines again for non-small -n values. Previously it may have output too few lines. [bug introduced in coreutils-9.8] diff --git a/src/sort.c b/src/sort.c index 7127f671b6..78b2f69f96 100644 --- a/src/sort.c +++ b/src/sort.c @@ -371,12 +371,57 @@ static bool debug; number are present, temp files will be used. */ static unsigned int nmerge = NMERGE_DEFAULT; +/* Whether SIGPIPE had the default disposition at startup. */ +static bool default_SIGPIPE; + +/* The list of temporary files. */ +struct tempnode +{ + struct tempnode *volatile next; + pid_t pid; /* The subprocess PID; undefined if state == UNCOMPRESSED. */ + char state; + char name[FLEXIBLE_ARRAY_MEMBER]; +}; +static struct tempnode *volatile temphead; +static struct tempnode *volatile *temptail = &temphead; + +/* Clean up any remaining temporary files. */ + +static void +cleanup (void) +{ + struct tempnode const *node; + + for (node = temphead; node; node = node->next) + unlink (node->name); + temphead = nullptr; +} + +/* Handle interrupts and hangups. */ + +static void +sighandler (int sig) +{ + if (! SA_NOCLDSTOP) + signal (sig, SIG_IGN); + + cleanup (); + + signal (sig, SIG_DFL); + raise (sig); +} + /* Report MESSAGE for FILE, then clean up and exit. If FILE is null, it represents standard output. */ static void sort_die (char const *message, char const *file) { + /* If we got EPIPE writing to stdout (from a previous fwrite() or fclose() + and SIGPIPE was originally SIG_DFL, mimic standard SIGPIPE behavior. */ + if (errno == EPIPE && !file && default_SIGPIPE) + sighandler (SIGPIPE); + error (SORT_FAILURE, errno, "%s: %s", message, quotef (file ? file : _("standard output"))); } @@ -631,17 +676,6 @@ cs_leave (struct cs_status const *status) the subprocess to finish. */ enum { UNCOMPRESSED, UNREAPED, REAPED }; -/* The list of temporary files. */ -struct tempnode -{ - struct tempnode *volatile next; - pid_t pid; /* The subprocess PID; undefined if state == UNCOMPRESSED. */ - char state; - char name[FLEXIBLE_ARRAY_MEMBER]; -}; -static struct tempnode *volatile temphead; -static struct tempnode *volatile *temptail = &temphead; - /* A file to be sorted. */ struct sortfile { @@ -780,18 +814,6 @@ reap_all (void) reap (-1); } -/* Clean up any remaining temporary files. */ - -static void -cleanup (void) -{ - struct tempnode const *node; - - for (node = temphead; node; node = node->next) - unlink (node->name); - temphead = nullptr; -} - /* Cleanup actions to take when exiting. */ static void @@ -4262,20 +4284,6 @@ parse_field_count (char const *string, size_t *val, char const *msgid) return suffix; } -/* Handle interrupts and hangups. */ - -static void -sighandler (int sig) -{ - if (! SA_NOCLDSTOP) - signal (sig, SIG_IGN); - - cleanup (); - - signal (sig, SIG_DFL); - raise (sig); -} - /* Set the ordering options for KEY specified in S. Return the address of the first character in S that is not a valid ordering option. @@ -4409,7 +4417,7 @@ main (int argc, char **argv) static int const sig[] = { /* The usual suspects. */ - SIGALRM, SIGHUP, SIGINT, SIGPIPE, SIGQUIT, SIGTERM, + SIGALRM, SIGHUP, SIGINT, SIGQUIT, SIGTERM, #ifdef SIGPOLL SIGPOLL, #endif @@ -4457,6 +4465,11 @@ main (int argc, char **argv) } signal (SIGCHLD, SIG_DFL); /* Don't inherit CHLD handling from parent. */ + /* Ignore SIGPIPE so write failures are reported via EPIPE errno. + For stdout, sort_die() will reraise SIGPIPE if it was originally SIG_DFL. + For compression pipes, sort_die() will exit with SORT_FAILURE. */ + default_SIGPIPE = (signal (SIGPIPE, SIG_IGN) == SIG_DFL); + /* The signal mask is known, so it is safe to invoke exit_cleanup. */ atexit (exit_cleanup); diff --git a/tests/sort/sort-compress-proc.sh b/tests/sort/sort-compress-proc.sh index c410b7fce0..2033429244 100755 --- a/tests/sort/sort-compress-proc.sh +++ b/tests/sort/sort-compress-proc.sh @@ -17,7 +17,7 @@ # along with this program. If not, see . . "${srcdir=.}/tests/init.sh"; path_prepend_ ./src -print_ver_ sort +print_ver_ sort kill expensive_ # Terminate any background processes @@ -25,9 +25,9 @@ cleanup_() { kill $pid 2>/dev/null && wait $pid; } SORT_FAILURE=2 -seq -w 2000 > exp || fail=1 -tac exp > in || fail=1 -insize=$(stat -c %s - exp || framework_failure_ +tac exp > in || framework_failure_ +insize=$(stat -c %s - out +test $(env kill -l $?) = 'PIPE' && fail=1 + # "Impatient exit" tests # # In these test cases, the biggest compressor (or decompressor) exits