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")));
}
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
{
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
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.
static int const sig[] =
{
/* The usual suspects. */
- SIGALRM, SIGHUP, SIGINT, SIGPIPE, SIGQUIT, SIGTERM,
+ SIGALRM, SIGHUP, SIGINT, SIGQUIT, SIGTERM,
#ifdef SIGPOLL
SIGPOLL,
#endif
}
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);
# along with this program. If not, see <https://www.gnu.org/licenses/>.
. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src
-print_ver_ sort
+print_ver_ sort kill
expensive_
# Terminate any background processes
SORT_FAILURE=2
-seq -w 2000 > exp || fail=1
-tac exp > in || fail=1
-insize=$(stat -c %s - <in) || fail=1
+seq -w 2000 > exp || framework_failure_
+tac exp > in || framework_failure_
+insize=$(stat -c %s - <in) || framework_failure_
# This compressor's behavior is adjustable via environment variables.
export PRE_COMPRESS=
chmod +x compress
+# "Early exit" test
+#
+# In this test, the compressor exits before reading all (any) data.
+# Until coreutils 9.9 'sort' could get a SIGPIPE writing to the
+# exited processes and silently exit. Note the same issue can happen
+# irrespective of exit status. It's more likely to happen in the
+# case of the child exiting with success, and if we write more data
+# (hence the --batch-size=30 and double "in"). Note we check sort doesn't
+# get SIGPIPE rather than if it returns SORT_FAILURE, because there is
+# the theoretical possibility that the kernel could buffer the
+# amount of data we're writing here and not issue the EPIPE to sort.
+# In other words we currently may not detect failures in the extreme edge case
+# of writing a small amount of data to a compressor that exits 0
+# while not reading all the data presented.
+PRE_COMPRESS='exit 0' \
+ sort --compress-program=./compress -S 1k --batch-size=30 ./in ./in > out
+test $(env kill -l $?) = 'PIPE' && fail=1
+
# "Impatient exit" tests
#
# In these test cases, the biggest compressor (or decompressor) exits