summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ChangeLog129
-rw-r--r--Makefile.am3
-rw-r--r--Makefile.maint2
-rw-r--r--NEWS13
-rw-r--r--bootstrap.conf3
-rw-r--r--configure.ac2
-rw-r--r--doc/coreutils.texi35
-rw-r--r--lib/.cvsignore4
-rw-r--r--lib/.gitignore4
-rw-r--r--m4/.cvsignore2
-rw-r--r--m4/.gitignore2
-rw-r--r--src/sort.c591
-rw-r--r--tests/misc/Makefile.am5
-rwxr-xr-xtests/misc/pwd-long38
-rwxr-xr-xtests/misc/sort-compress92
-rw-r--r--tests/sort/Makefile.am72
-rwxr-xr-xtests/sort/Test.pm4
17 files changed, 885 insertions, 116 deletions
diff --git a/ChangeLog b/ChangeLog
index 70ca3ec79..09b7dbb4a 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,132 @@
+2007-01-26 Jim Meyering <jim@meyering.net>
+
+ * .x-sc_cast_of_argument_to_free: Remove this file.
+ * Makefile.am (EXTRA_DIST): Likewise.
+
+2007-01-25 Dan Hipschman <dsh@linux.ucla.edu>
+
+ * src/sort.c (create_temp): Remove superfluous access-X_OK
+ check. find_in_path does this for us.
+
+2007-01-24 Jim Meyering <jim@meyering.net>
+
+ Remove usually-skipped test.
+ * tests/cp/open-perm-race: Remove this file. It is subsumed
+ by parent-perm-race.
+ * tests/cp/Makefile.am (TESTS): Remove open-perm-race.
+ * tests/sort/Makefile.am: Regenerate.
+
+ Pass "make distcheck" again.
+ * src/sort.c (usage): Split a diagnostic that had grown to be
+ longer than the C89 maximum of 509 bytes.
+ * .x-sc_cast_of_argument_to_free: New file. Allow a cast in sort.c.
+ FIXME: this is just temporary, while we wait to remove the offending
+ access-calling code.
+ * Makefile.am (EXTRA_DIST): Add .x-sc_cast_of_argument_to_free.
+ * Makefile.maint (sc_cast_of_argument_to_free): Use the
+ canonical, $$($(CVS_LIST_EXCEPT)).
+ * m4/.gitignore, m4/.cvsignore, lib/.gitignore, lib/.cvsignore: Update.
+
+2007-01-24 Paul Eggert <eggert@cs.ucla.edu>
+
+ * NEWS: New option sort -C, proposed by XCU ERN 127, which looks
+ like it will be approved. Also add --check=quiet, --check=silent
+ as long aliases, and --check=diagnose-first as an alias for -c.
+ * doc/coreutils.texi (sort invocation): Document this.
+ Also, mention that sort -c can take at most one file.
+ * src/sort.c: Implement this.
+ Include argmatch.h.
+ (usage): Document the change.
+ (CHECK_OPTION): New constant.
+ (long_options): --check now takes an optional argument, and is now
+ treated differently from 'c'.
+ (check_args, check_types): New constant arrays.
+ (check): New arg CHECKONLY, which suppresses diagnostic if -C.
+ (main): Parse the new options.
+ * tests/sort/Test.pm (02d, 02d, incompat5, incompat6):
+ New tests for -C.
+
+2007-01-24 Jim Meyering <jim@meyering.net>
+
+ Fix a typo.
+ * tests/misc/sort-compress: Use $abs_top_builddir, not $top_builddir.
+ * tests/misc/Makefile.am (TESTS_ENVIRONMENT): Likewise.
+
+ Don't depend on "which".
+ * tests/misc/sort-compress (SORT): Use $abs_builddir, now which.
+ * tests/misc/Makefile.am (TESTS_ENVIRONMENT): Export top_builddir.
+
+2007-01-24 Dan Hipschman <dsh@linux.ucla.edu>
+
+ Test sort compression.
+ * tests/misc/Makefile.am: Add the test.
+ * tests/misc/sort-compress: New file containing the tests.
+
+2007-01-24 Jim Meyering <jim@meyering.net>
+
+ * NEWS: sort temp file compression: tweak wording.
+ * src/sort.c (struct sortfile) [name]: Declare member to be const.
+
+2007-01-21 Jim Meyering <jim@meyering.net>
+
+ * src/sort.c (MAX_FORK_RETRIES_COMPRESS, MAX_FORK_RETRIES_DECOMPRESS):
+ In pipe_fork callers, use these named constants, not "2" and "8".
+ (proctab, nprocs): Declare to be "static".
+ (pipe_fork) [lint]: Initialize local, pid,
+ to avoid unwarranted may-be-used-uninitialized warning.
+ (create_temp): Use the active voice. Describe parameters, too.
+
+2007-01-21 James Youngman <jay@gnu.org>
+
+ Centralize all the uses of sigprocmask(). Don't restore an invalid
+ saved mask.
+ * src/sort.c (enter_cs, leave_cs): New functions for protecting
+ code sequences against signal delivery.
+ * (exit_cleanup): Use enter_cs and leave_cs instead of
+ calling sigprocmask directly.
+ (create_temp_file, pipe_fork, zaptemp): Likewise
+
+2007-01-21 Dan Hipschman <dsh@linux.ucla.edu>
+
+ Add compression of temp files to sort.
+ * NEWS: Mention this.
+ * bootstrap.conf: Import findprog.
+ * configure.ac: Add AC_FUNC_FORK.
+ * doc/coreutils.texi: Document GNUSORT_COMPRESSOR environment
+ variable.
+ * src/sort.c (compress_program): New global, holds the name of the
+ external compression program.
+ (struct sortfile): New type used by mergepfs and friends instead
+ of filenames to hold PIDs of compressor processes.
+ (proctab): New global, holds compressor PIDs on which to wait.
+ (enum procstate, struct procnode): New types used by proctab.
+ (proctab_hasher, proctab_comparator): New functions for proctab.
+ (nprocs): New global, number of forked but unreaped children.
+ (reap, reap_some): New function, wait for/cleanup forked processes.
+ (register_proc, update_proc, wait_proc): New functions for adding,
+ modifying and removing proctab entries.
+ (create_temp_file): Change parameter type to pointer to file
+ descriptor, and return type to pointer to struct tempnode.
+ (dup2_or_die): New function used in create_temp and open_temp.
+ (pipe_fork): New function, creates a pipe and child process.
+ (create_temp): Creates a temp file and possibly a compression
+ program to which we filter output.
+ (open_temp): Opens a compressed temp file and creates a
+ decompression process through which to filter the input.
+ (mergefps): Change FILES parameter type to struct sortfile array
+ and update access accordingly. Use open_temp and reap_some.
+ (avoid_trashing_input, merge): Change FILES parameter like
+ mergefps and call create_temp instead of create_temp_file.
+ (sort): Call create_temp instead of create_temp_file.
+ Use reap_some.
+ (avoid_trashing_input, merge, sort, main): Adapt to mergefps.
+
+2007-01-20 Jim Meyering <jim@meyering.net>
+
+ * tests/misc/pwd-long: Work properly even when run from the
+ wrong one of two or more bind-mounted sibling directories.
+ Suggestion from Mike Stone in <http://bugs.debian.org/380552>.
+
2007-01-20 Paul Eggert <eggert@cs.ucla.edu>
Standardize on list of signals when an app catches signals.
diff --git a/Makefile.am b/Makefile.am
index 9eeeb2037..1a6307717 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -1,7 +1,6 @@
# Make coreutils. -*-Makefile-*-
-# Copyright (C) 1990, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000,
-# 2001, 2002, 2003, 2004, 2005, 2006 Free Software Foundation, Inc.
+# Copyright (C) 1990, 1993-2007 Free Software Foundation, Inc.
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
diff --git a/Makefile.maint b/Makefile.maint
index 9baafa3d8..d526baacd 100644
--- a/Makefile.maint
+++ b/Makefile.maint
@@ -98,7 +98,7 @@ syntax-check: $(syntax-check-rules)
# FIXME: don't allow `#include .strings\.h' anywhere
sc_cast_of_argument_to_free:
- @grep -nE '\<free \(\(' $(srcdir)/{lib,src}/*.[chly] && \
+ @grep -nE '\<free \(\(' $$($(CVS_LIST_EXCEPT)) && \
{ echo '$(ME): don'\''t cast free argument' 1>&2; \
exit 1; } || :
diff --git a/NEWS b/NEWS
index a30b17e52..ad0fd1d24 100644
--- a/NEWS
+++ b/NEWS
@@ -29,6 +29,19 @@ GNU coreutils NEWS -*- outline -*-
"rm --interactive=never F" no longer prompts for an unwritable F
+** New features
+
+ By default, sort usually compresses each temporary file it writes.
+ When sorting very large inputs, this can result in sort using far
+ less temporary disk space and in improved performance.
+
+** New features
+
+ sort accepts the new option -C, which acts like -c except no diagnostic
+ is printed. Its --check option now accepts an optional argument, and
+ --check=quiet and --check=silent are now aliases for -C, while
+ --check=diagnose-first is an alias for -c or plain --check.
+
* Noteworthy changes in release 6.7 (2006-12-08) [stable]
diff --git a/bootstrap.conf b/bootstrap.conf
index 73e7d4926..b262622e0 100644
--- a/bootstrap.conf
+++ b/bootstrap.conf
@@ -43,7 +43,8 @@ gnulib_modules="
config-h configmake
closeout cycle-check d-ino d-type diacrit dirfd dirname dup2
error euidaccess exclude exitfail fchdir fcntl fcntl-safer fdl
- file-type fileblocks filemode filenamecat fnmatch-gnu fopen-safer
+ file-type fileblocks filemode filenamecat findprog fnmatch-gnu
+ fopen-safer
fprintftime fsusage ftruncate fts getdate getgroups gethrxtime
getline getloadavg getndelim2 getopt getpagesize getpass-gnu
gettext gettime gettimeofday getugroups getusershell gnupload
diff --git a/configure.ac b/configure.ac
index 25b23b597..66f079b40 100644
--- a/configure.ac
+++ b/configure.ac
@@ -39,6 +39,8 @@ gl_EARLY
gl_INIT
coreutils_MACROS
+AC_FUNC_FORK
+
AC_CHECK_FUNCS(uname,
OPTIONAL_BIN_PROGS="$OPTIONAL_BIN_PROGS uname\$(EXEEXT)"
MAN="$MAN uname.1")
diff --git a/doc/coreutils.texi b/doc/coreutils.texi
index 89e97d8bc..ff049bcf7 100644
--- a/doc/coreutils.texi
+++ b/doc/coreutils.texi
@@ -3342,12 +3342,26 @@ mode:
@item -c
@itemx --check
+@itemx --check=diagnose-first
@opindex -c
@opindex --check
@cindex checking for sortedness
-Check whether the given files are already sorted: if they are not all
-sorted, print an error message and exit with a status of 1.
+Check whether the given file is already sorted: if it is not all
+sorted, print a diagnostic containing the first out-of-order line and
+exit with a status of 1.
Otherwise, exit successfully.
+At most one input file can be given.
+
+@item -C
+@itemx --check=quiet
+@itemx --check=silent
+@opindex -c
+@opindex --check
+@cindex checking for sortedness
+Exit successfully if the given file is already sorted, and
+exit with status 1 otherwise.
+At most one input file can be given.
+This is like @option{-c}, except it does not print a diagnostic.
@item -m
@itemx --merge
@@ -3401,7 +3415,7 @@ Exit status:
@display
0 if no error occurred
-1 if invoked with @option{-c} and the input is not properly sorted
+1 if invoked with @option{-c} or @option{-C} and the input is not sorted
2 if an error occurred
@end display
@@ -3411,6 +3425,19 @@ value as the directory for temporary files instead of @file{/tmp}. The
@option{--temporary-directory} (@option{-T}) option in turn overrides
the environment variable.
+@vindex GNUSORT_COMPRESSOR
+To improve performance when sorting very large files, GNU sort will,
+by default, try to compress temporary files with the program
+@file{gzip}. The environment variable @env{GNUSORT_COMPRESSOR} can be
+set to the name of another program to be used. The program specified
+must compress standard input to standard output when no arguments are
+given to it, and it must decompress standard input to standard output
+when the @option{-d} argument is given to it. If the program exits
+with nonzero status, sort will terminate with an error. To disable
+compression of temporary files, set the variable to the empty string.
+Whitespace and the backslash character should not appear in the
+program name. They are reserved for future use.
+
The following options affect the ordering of output lines. They may be
specified globally or as part of a specific key field. If no key
@@ -3703,7 +3730,7 @@ disks and controllers.
@cindex uniquifying output
Normally, output only the first of a sequence of lines that compare
-equal. For the @option{--check} (@option{-c}) option,
+equal. For the @option{--check} (@option{-c} or @option{-C}) option,
check that no pair of consecutive lines compares equal.
This option also disables the default last-resort comparison.
diff --git a/lib/.cvsignore b/lib/.cvsignore
index fb5f9e0ea..28dc243ee 100644
--- a/lib/.cvsignore
+++ b/lib/.cvsignore
@@ -40,6 +40,7 @@ close-stream.c
close-stream.h
closeout.c
closeout.h
+concatpath.c
config.charset
config.h
config.hin
@@ -85,6 +86,8 @@ filemode.c
filemode.h
filenamecat.c
filenamecat.h
+findprog.c
+findprog.h
fnmatch.c
fnmatch.h
fnmatch_.h
@@ -220,6 +223,7 @@ openat-proc.c
openat.c
openat.h
pathmax.h
+pathname.h
physmem.c
physmem.h
pipe-safer.c
diff --git a/lib/.gitignore b/lib/.gitignore
index 1ebe1d47d..873c4ce7a 100644
--- a/lib/.gitignore
+++ b/lib/.gitignore
@@ -37,6 +37,7 @@ close-stream.c
close-stream.h
closeout.c
closeout.h
+concatpath.c
config.charset
configmake.h
creat-safer.c
@@ -80,6 +81,8 @@ filemode.c
filemode.h
filenamecat.c
filenamecat.h
+findprog.c
+findprog.h
fnmatch.c
fnmatch.h
fnmatch_.h
@@ -214,6 +217,7 @@ openat-proc.c
openat.c
openat.h
pathmax.h
+pathname.h
physmem.c
physmem.h
pipe-safer.c
diff --git a/m4/.cvsignore b/m4/.cvsignore
index 0311ce8c1..74c32371b 100644
--- a/m4/.cvsignore
+++ b/m4/.cvsignore
@@ -30,6 +30,7 @@ dirname.m4
dos.m4
double-slash-root.m4
dup2.m4
+eaccess.m4
eealloc.m4
eoverflow.m4
error.m4
@@ -44,6 +45,7 @@ file-type.m4
fileblocks.m4
filemode.m4
filenamecat.m4
+findprog.m4
flexmember.m4
fnmatch.m4
fpending.m4
diff --git a/m4/.gitignore b/m4/.gitignore
index e6ae77991..1916f1862 100644
--- a/m4/.gitignore
+++ b/m4/.gitignore
@@ -29,6 +29,7 @@ dirname.m4
dos.m4
double-slash-root.m4
dup2.m4
+eaccess.m4
eealloc.m4
eoverflow.m4
error.m4
@@ -43,6 +44,7 @@ file-type.m4
fileblocks.m4
filemode.m4
filenamecat.m4
+findprog.m4
flexmember.m4
fnmatch.m4
fpending.m4
diff --git a/src/sort.c b/src/sort.c
index 8a2279637..c7ae0c8c0 100644
--- a/src/sort.c
+++ b/src/sort.c
@@ -1,5 +1,5 @@
/* sort - sort lines of text (with all kinds of options).
- Copyright (C) 1988, 1991-2006 Free Software Foundation, Inc.
+ Copyright (C) 1988, 1991-2007 Free Software Foundation, Inc.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -25,10 +25,14 @@
#include <getopt.h>
#include <sys/types.h>
+#include <sys/wait.h>
#include <signal.h>
#include "system.h"
+#include "argmatch.h"
#include "error.h"
+#include "findprog.h"
#include "hard-locale.h"
+#include "hash.h"
#include "inttostr.h"
#include "md5.h"
#include "physmem.h"
@@ -63,7 +67,8 @@ struct rlimit { size_t rlim_cur; };
present. */
#ifndef SA_NOCLDSTOP
# define SA_NOCLDSTOP 0
-# define sigprocmask(How, Set, Oset) /* empty */
+/* No sigprocmask. Always 'return' zero. */
+# define sigprocmask(How, Set, Oset) (0)
# define sigset_t int
# if ! HAVE_SIGINTERRUPT
# define siginterrupt(sig, flag) /* empty */
@@ -92,6 +97,20 @@ enum
SORT_FAILURE = 2
};
+enum
+ {
+ /* The number of times we should try to fork a compression process
+ (we retry if the fork call fails). We don't _need_ to compress
+ temp files, this is just to reduce disk access, so this number
+ can be small. */
+ MAX_FORK_TRIES_COMPRESS = 2,
+
+ /* The number of times we should try to fork a decompression process.
+ If we can't fork a decompression process, we can't sort, so this
+ number should be big. */
+ MAX_FORK_TRIES_DECOMPRESS = 8
+ };
+
/* The representation of the decimal point in the current locale. */
static int decimal_point;
@@ -261,6 +280,9 @@ static bool have_read_stdin;
/* List of key field comparisons to be tried. */
static struct keyfield *keylist;
+/* Program used to (de)compress temp files. Must accept -d. */
+static const char *compress_program;
+
static void sortlines_temp (struct line *, size_t, struct line *);
/* Report MESSAGE for FILE, then clean up and exit.
@@ -315,9 +337,12 @@ Ordering options:\n\
fputs (_("\
Other options:\n\
\n\
- -c, --check check whether input is sorted; do not sort\n\
+ -c, --check, --check=diagnose-first check for sorted input; do not sort\n\
+ -C, --check=quiet, --check=silent like -c, but do not report first bad line\n\
-k, --key=POS1[,POS2] start a key at POS1, end it at POS2 (origin 1)\n\
-m, --merge merge already sorted files; do not sort\n\
+"), stdout);
+ fputs (_("\
-o, --output=FILE write result to FILE instead of standard output\n\
-s, --stable stabilize sort by disabling last-resort comparison\n\
-S, --buffer-size=SIZE use SIZE for main memory buffer\n\
@@ -364,15 +389,16 @@ native byte values.\n\
non-character as a pseudo short option, starting with CHAR_MAX + 1. */
enum
{
- RANDOM_SOURCE_OPTION = CHAR_MAX + 1
+ CHECK_OPTION = CHAR_MAX + 1,
+ RANDOM_SOURCE_OPTION
};
-static char const short_options[] = "-bcdfgik:mMno:rRsS:t:T:uy:z";
+static char const short_options[] = "-bcCdfgik:mMno:rRsS:t:T:uy:z";
static struct option const long_options[] =
{
{"ignore-leading-blanks", no_argument, NULL, 'b'},
- {"check", no_argument, NULL, 'c'},
+ {"check", optional_argument, NULL, CHECK_OPTION},
{"dictionary-order", no_argument, NULL, 'd'},
{"ignore-case", no_argument, NULL, 'f'},
{"general-numeric-sort", no_argument, NULL, 'g'},
@@ -396,18 +422,220 @@ static struct option const long_options[] =
{NULL, 0, NULL, 0},
};
+static char const *const check_args[] =
+{
+ "quiet", "silent", "diagnose-first", NULL
+};
+static char const check_types[] =
+{
+ 'C', 'C', 'c'
+};
+ARGMATCH_VERIFY (check_args, check_types);
+
/* The set of signals that are caught. */
static sigset_t caught_signals;
+/* Critical section status. */
+struct cs_status
+{
+ bool valid;
+ sigset_t sigs;
+};
+
+/* Enter a critical section. */
+static struct cs_status
+cs_enter (void)
+{
+ struct cs_status status;
+ status.valid = (sigprocmask (SIG_BLOCK, &caught_signals, &status.sigs) == 0);
+ return status;
+}
+
+/* Leave a critical section. */
+static void
+cs_leave (struct cs_status status)
+{
+ if (status.valid)
+ {
+ /* Ignore failure when restoring the signal mask. */
+ sigprocmask (SIG_SETMASK, &status.sigs, NULL);
+ }
+}
+
/* The list of temporary files. */
struct tempnode
{
struct tempnode *volatile next;
+ pid_t pid; /* If compressed, the pid of compressor, else zero */
char name[1]; /* Actual size is 1 + file name length. */
};
static struct tempnode *volatile temphead;
static struct tempnode *volatile *temptail = &temphead;
+struct sortfile
+{
+ char const *name;
+ pid_t pid; /* If compressed, the pid of compressor, else zero */
+};
+
+/* A table where we store compression process states. We clean up all
+ processes in a timely manner so as not to exhaust system resources,
+ so we store the info on whether the process is still running, or has
+ been reaped here. */
+static Hash_table *proctab;
+
+enum { INIT_PROCTAB_SIZE = 47 };
+
+enum procstate { ALIVE, ZOMBIE };
+
+/* A proctab entry. The COUNT field is there in case we fork a new
+ compression process that has the same PID as an old zombie process
+ that is still in the table (because the process to decompress the
+ temp file it was associated with hasn't started yet). */
+struct procnode
+{
+ pid_t pid;
+ enum procstate state;
+ size_t count;
+};
+
+static size_t
+proctab_hasher (const void *entry, size_t tabsize)
+{
+ const struct procnode *node = entry;
+ return node->pid % tabsize;
+}
+
+static bool
+proctab_comparator (const void *e1, const void *e2)
+{
+ const struct procnode *n1 = e1, *n2 = e2;
+ return n1->pid == n2->pid;
+}
+
+/* The total number of forked processes (compressors and decompressors)
+ that have not been reaped yet. */
+static size_t nprocs;
+
+/* The number of child processes we'll allow before we try to reap some. */
+enum { MAX_PROCS_BEFORE_REAP = 2 };
+
+/* If 0 < PID, wait for the child process with that PID to exit.
+ If PID is -1, clean up a random child process which has finished and
+ return the process ID of that child. If PID is -1 and no processes
+ have quit yet, return 0 without waiting. */
+
+static pid_t
+reap (pid_t pid)
+{
+ int status;
+ pid_t cpid = waitpid (pid, &status, pid < 0 ? WNOHANG : 0);
+
+ if (cpid < 0)
+ error (SORT_FAILURE, errno, _("waiting for %s [-d]"),
+ compress_program);
+ else if (0 < cpid)
+ {
+ if (! WIFEXITED (status) || WEXITSTATUS (status))
+ error (SORT_FAILURE, 0, _("%s [-d] terminated abnormally"),
+ compress_program);
+ --nprocs;
+ }
+
+ return cpid;
+}
+
+/* Add the PID of a running compression process to proctab, or update
+ the entry COUNT and STATE fields if it's already there. This also
+ creates the table for us the first time it's called. */
+
+static void
+register_proc (pid_t pid)
+{
+ struct procnode test, *node;
+
+ if (! proctab)
+ {
+ proctab = hash_initialize (INIT_PROCTAB_SIZE, NULL,
+ proctab_hasher,
+ proctab_comparator,
+ free);
+ if (! proctab)
+ xalloc_die ();
+ }
+
+ test.pid = pid;
+ node = hash_lookup (proctab, &test);
+ if (node)
+ {
+ node->state = ALIVE;
+ ++node->count;
+ }
+ else
+ {
+ node = xmalloc (sizeof *node);
+ node->pid = pid;
+ node->state = ALIVE;
+ node->count = 1;
+ hash_insert (proctab, node);
+ }
+}
+
+/* This is called when we reap a random process. We don't know
+ whether we have reaped a compression process or a decompression
+ process until we look in the table. If there's an ALIVE entry for
+ it, then we have reaped a compression process, so change the state
+ to ZOMBIE. Otherwise, it's a decompression processes, so ignore it. */
+
+static void
+update_proc (pid_t pid)
+{
+ struct procnode test, *node;
+
+ test.pid = pid;
+ node = hash_lookup (proctab, &test);
+ if (node)
+ node->state = ZOMBIE;
+}
+
+/* This is for when we need to wait for a compression process to exit.
+ If it has a ZOMBIE entry in the table then it's already dead and has
+ been reaped. Note that if there's an ALIVE entry for it, it still may
+ already have died and been reaped if a second process was created with
+ the same PID. This is probably exceedingly rare, but to be on the safe
+ side we will have to wait for any compression process with this PID. */
+
+static void
+wait_proc (pid_t pid)
+{
+ struct procnode test, *node;
+
+ test.pid = pid;
+ node = hash_lookup (proctab, &test);
+ if (node->state == ALIVE)
+ reap (pid);
+
+ node->state = ZOMBIE;
+ if (! --node->count)
+ {
+ hash_delete (proctab, node);
+ free (node);
+ }
+}
+
+/* Keep reaping finished children as long as there are more to reap.
+ This doesn't block waiting for any of them, it only reaps those
+ that are already dead. */
+
+static void
+reap_some (void)
+{
+ pid_t pid;
+
+ while (0 < nprocs && (pid = reap (-1)))
+ update_proc (pid);
+}
+
/* Clean up any remaining temporary files. */
static void
@@ -429,24 +657,22 @@ exit_cleanup (void)
{
/* Clean up any remaining temporary files in a critical section so
that a signal handler does not try to clean them too. */
- sigset_t oldset;
- sigprocmask (SIG_BLOCK, &caught_signals, &oldset);
+ struct cs_status cs = cs_enter ();
cleanup ();
- sigprocmask (SIG_SETMASK, &oldset, NULL);
+ cs_leave (cs);
}
close_stdout ();
}
-/* Create a new temporary file, returning its newly allocated name.
- Store into *PFP a stream open for writing. */
+/* Create a new temporary file, returning its newly allocated tempnode.
+ Store into *PFD the file descriptor open for writing. */
-static char *
-create_temp_file (FILE **pfp)
+static struct tempnode *
+create_temp_file (int *pfd)
{
static char const slashbase[] = "/sortXXXXXX";
static size_t temp_dir_index;
- sigset_t oldset;
int fd;
int saved_errno;
char const *temp_dir = temp_dirs[temp_dir_index];
@@ -454,15 +680,17 @@ create_temp_file (FILE **pfp)
struct tempnode *node =
xmalloc (offsetof (struct tempnode, name) + len + sizeof slashbase);
char *file = node->name;
+ struct cs_status cs;
memcpy (file, temp_dir, len);
memcpy (file + len, slashbase, sizeof slashbase);
node->next = NULL;
+ node->pid = 0;
if (++temp_dir_index == temp_dir_count)
temp_dir_index = 0;
/* Create the temporary file in a critical section, to avoid races. */
- sigprocmask (SIG_BLOCK, &caught_signals, &oldset);
+ cs = cs_enter ();
fd = mkstemp (file);
if (0 <= fd)
{
@@ -470,13 +698,14 @@ create_temp_file (FILE **pfp)
temptail = &node->next;
}
saved_errno = errno;
- sigprocmask (SIG_SETMASK, &oldset, NULL);
+ cs_leave (cs);
errno = saved_errno;
- if (fd < 0 || (*pfp = fdopen (fd, "w")) == NULL)
+ if (fd < 0)
die (_("cannot create temporary file"), file);
- return file;
+ *pfd = fd;
+ return node;
}
/* Return a stream for FILE, opened with mode HOW. A null FILE means
@@ -534,6 +763,192 @@ xfclose (FILE *fp, char const *file)
}
static void
+dup2_or_die (int oldfd, int newfd)
+{
+ if (dup2 (oldfd, newfd) < 0)
+ error (SORT_FAILURE, errno, _("dup2 failed"));
+}
+
+/* Fork a child process for piping to and do common cleanup. The
+ TRIES parameter tells us how many times to try to fork before
+ giving up. Return the PID of the child or -1 if fork failed. */
+
+static pid_t
+pipe_fork (int pipefds[2], size_t tries)
+{
+#if HAVE_WORKING_FORK
+ struct tempnode *saved_temphead;
+ int saved_errno;
+ unsigned int wait_retry = 1;
+ pid_t pid IF_LINT (= -1);
+ struct cs_status cs;
+
+ if (pipe (pipefds) < 0)
+ return -1;
+
+ while (tries--)
+ {
+ /* This is so the child process won't delete our temp files
+ if it receives a signal before exec-ing. */
+ cs = cs_enter ();
+ saved_temphead = temphead;
+ temphead = NULL;
+
+ pid = fork ();
+ saved_errno = errno;
+ if (pid)
+ temphead = saved_temphead;
+
+ cs_leave (cs);
+ errno = saved_errno;
+
+ if (0 <= pid || errno != EAGAIN)
+ break;
+ else
+ {
+ sleep (wait_retry);
+ wait_retry *= 2;
+ reap_some ();
+ }
+ }
+
+ if (pid < 0)
+ {
+ close (pipefds[0]);
+ close (pipefds[1]);
+ }
+ else if (pid == 0)
+ {
+ close (STDIN_FILENO);
+ close (STDOUT_FILENO);
+ }
+ else
+ ++nprocs;
+
+ return pid;
+
+#else /* ! HAVE_WORKING_FORK */
+ return -1;
+#endif
+}
+
+/* Create a temporary file and start a compression program to filter output
+ to that file. Set *PFP to the file handle and if *PPID is non-NULL,
+ set it to the PID of the newly-created process. */
+
+static char *
+create_temp (FILE **pfp, pid_t *ppid)
+{
+ static bool compress_program_known;
+ int tempfd;
+ struct tempnode *node = create_temp_file (&tempfd);
+ char *name = node->name;
+
+ if (! compress_program_known)
+ {
+ compress_program = getenv ("GNUSORT_COMPRESSOR");
+ if (compress_program == NULL)
+ {
+ static const char *default_program = "gzip";
+ const char *path_program = find_in_path (default_program);
+
+ if (path_program != default_program)
+ compress_program = path_program;
+ }
+ else if (*compress_program == '\0')
+ compress_program = NULL;
+
+ compress_program_known = true;
+ }
+
+ if (compress_program)
+ {
+ int pipefds[2];
+
+ node->pid = pipe_fork (pipefds, MAX_FORK_TRIES_COMPRESS);
+ if (0 < node->pid)
+ {
+ close (tempfd);
+ close (pipefds[0]);
+ tempfd = pipefds[1];
+
+ register_proc (node->pid);
+ }
+ else if (node->pid == 0)
+ {
+ close (pipefds[1]);
+ dup2_or_die (tempfd, STDOUT_FILENO);
+ close (tempfd);
+ dup2_or_die (pipefds[0], STDIN_FILENO);
+ close (pipefds[0]);
+
+ if (execlp (compress_program, compress_program,
+ (char *) NULL) < 0)
+ error (SORT_FAILURE, errno, _("couldn't execute %s"),
+ compress_program);
+ }
+ else
+ node->pid = 0;
+ }
+
+ *pfp = fdopen (tempfd, "w");
+ if (! *pfp)
+ die (_("couldn't create temporary file"), name);
+
+ if (ppid)
+ *ppid = node->pid;
+
+ return name;
+}
+
+/* Open a compressed temp file and start a decompression process through
+ which to filter the input. PID must be the valid processes ID of the
+ process used to compress the file. */
+
+static FILE *
+open_temp (const char *name, pid_t pid)
+{
+ int tempfd, pipefds[2];
+ pid_t child_pid;
+ FILE *fp;
+
+ wait_proc (pid);
+
+ tempfd = open (name, O_RDONLY);
+ if (tempfd < 0)
+ die (_("couldn't open temporary file"), name);
+
+ child_pid = pipe_fork (pipefds, MAX_FORK_TRIES_DECOMPRESS);
+ if (0 < child_pid)
+ {
+ close (tempfd);
+ close (pipefds[1]);
+ }
+ else if (child_pid == 0)
+ {
+ close (pipefds[0]);
+ dup2_or_die (tempfd, STDIN_FILENO);
+ close (tempfd);
+ dup2_or_die (pipefds[1], STDOUT_FILENO);
+ close (pipefds[1]);
+
+ if (execlp (compress_program, compress_program,
+ "-d", (char *) NULL) < 0)
+ error (SORT_FAILURE, errno, _("couldn't execute %s -d"),
+ compress_program);
+ }
+ else
+ error (SORT_FAILURE, errno, _("couldn't create process for %s -d"),
+ compress_program);
+
+ fp = fdopen (pipefds[0], "r");
+ if (! fp)
+ die (_("couldn't create temporary file"), name);
+
+ return fp;
+}
+
+static void
write_bytes (const char *buf, size_t n_bytes, FILE *fp, const char *output_file)
{
if (fwrite (buf, 1, n_bytes, fp) != n_bytes)
@@ -558,20 +973,20 @@ zaptemp (const char *name)
struct tempnode *volatile *pnode;
struct tempnode *node;
struct tempnode *next;
- sigset_t oldset;
int unlink_status;
int unlink_errno = 0;
+ struct cs_status cs;
for (pnode = &temphead; (node = *pnode)->name != name; pnode = &node->next)
continue;
/* Unlink the temporary file in a critical section to avoid races. */
next = node->next;
- sigprocmask (SIG_BLOCK, &caught_signals, &oldset);
+ cs = cs_enter ();
unlink_status = unlink (name);
unlink_errno = errno;
*pnode = next;
- sigprocmask (SIG_SETMASK, &oldset, NULL);
+ cs_leave (cs);
if (unlink_status != 0)
error (0, unlink_errno, _("warning: cannot remove: %s"), name);
@@ -1512,13 +1927,13 @@ compare (const struct line *a, const struct line *b)
return reverse ? -diff : diff;
}
-/* Check that the lines read from FILE_NAME come in order. Print a
- diagnostic (FILE_NAME, line number, contents of line) to stderr and return
- false if they are not in order. Otherwise, print no diagnostic
- and return true. */
+/* Check that the lines read from FILE_NAME come in order. Return
+ true if they are in order. If CHECKONLY == 'c', also print a
+ diagnostic (FILE_NAME, line number, contents of line) to stderr if
+ they are not in order. */
static bool
-check (char const *file_name)
+check (char const *file_name, char checkonly)
{
FILE *fp = xfopen (file_name, "r");
struct buffer buf; /* Input buffer. */
@@ -1544,15 +1959,19 @@ check (char const *file_name)
{
found_disorder:
{
- struct line const *disorder_line = line - 1;
- uintmax_t disorder_line_number =
- buffer_linelim (&buf) - disorder_line + line_number;
- char hr_buf[INT_BUFSIZE_BOUND (uintmax_t)];
- fprintf (stderr, _("%s: %s:%s: disorder: "),
- program_name, file_name,
- umaxtostr (disorder_line_number, hr_buf));
- write_bytes (disorder_line->text, disorder_line->length, stderr,
- _("standard error"));
+ if (checkonly == 'c')
+ {
+ struct line const *disorder_line = line - 1;
+ uintmax_t disorder_line_number =
+ buffer_linelim (&buf) - disorder_line + line_number;
+ char hr_buf[INT_BUFSIZE_BOUND (uintmax_t)];
+ fprintf (stderr, _("%s: %s:%s: disorder: "),
+ program_name, file_name,
+ umaxtostr (disorder_line_number, hr_buf));
+ write_bytes (disorder_line->text, disorder_line->length,
+ stderr, _("standard error"));
+ }
+
ordered = false;
break;
}
@@ -1605,7 +2024,7 @@ check (char const *file_name)
file has not been opened yet (or written to, if standard output). */
static void
-mergefps (char **files, size_t ntemps, size_t nfiles,
+mergefps (struct sortfile *files, size_t ntemps, size_t nfiles,
FILE *ofp, char const *output_file)
{
FILE *fps[NMERGE]; /* Input streams for each file. */
@@ -1628,10 +2047,12 @@ mergefps (char **files, size_t ntemps, size_t nfiles,
/* Read initial lines from each input file. */
for (i = 0; i < nfiles; )
{
- fps[i] = xfopen (files[i], "r");
+ fps[i] = (files[i].pid
+ ? open_temp (files[i].name, files[i].pid)
+ : xfopen (files[i].name, "r"));
initbuf (&buffer[i], sizeof (struct line),
MAX (merge_buffer_size, sort_size / nfiles));
- if (fillbuf (&buffer[i], fps[i], files[i]))
+ if (fillbuf (&buffer[i], fps[i], files[i].name))
{
struct line const *linelim = buffer_linelim (&buffer[i]);
cur[i] = linelim - 1;
@@ -1641,11 +2062,11 @@ mergefps (char **files, size_t ntemps, size_t nfiles,
else
{
/* fps[i] is empty; eliminate it from future consideration. */
- xfclose (fps[i], files[i]);
+ xfclose (fps[i], files[i].name);
if (i < ntemps)
{
ntemps--;
- zaptemp (files[i]);
+ zaptemp (files[i].name);
}
free (buffer[i].buf);
--nfiles;
@@ -1714,7 +2135,7 @@ mergefps (char **files, size_t ntemps, size_t nfiles,
cur[ord[0]] = smallest - 1;
else
{
- if (fillbuf (&buffer[ord[0]], fps[ord[0]], files[ord[0]]))
+ if (fillbuf (&buffer[ord[0]], fps[ord[0]], files[ord[0]].name))
{
struct line const *linelim = buffer_linelim (&buffer[ord[0]]);
cur[ord[0]] = linelim - 1;
@@ -1727,11 +2148,11 @@ mergefps (char **files, size_t ntemps, size_t nfiles,
if (ord[i] > ord[0])
--ord[i];
--nfiles;
- xfclose (fps[ord[0]], files[ord[0]]);
+ xfclose (fps[ord[0]], files[ord[0]].name);
if (ord[0] < ntemps)
{
ntemps--;
- zaptemp (files[ord[0]]);
+ zaptemp (files[ord[0]].name);
}
free (buffer[ord[0]].buf);
for (i = ord[0]; i < nfiles; ++i)
@@ -1774,6 +2195,10 @@ mergefps (char **files, size_t ntemps, size_t nfiles,
ord[j] = ord[j + 1];
ord[count_of_smaller_lines] = ord0;
}
+
+ /* Free up some resources every once in a while. */
+ if (MAX_PROCS_BEFORE_REAP < nprocs)
+ reap_some ();
}
if (unique && savedline)
@@ -1912,8 +2337,8 @@ sortlines_temp (struct line *lines, size_t nlines, struct line *temp)
common cases. */
static size_t
-avoid_trashing_input (char **files, size_t ntemps, size_t nfiles,
- char const *outfile)
+avoid_trashing_input (struct sortfile *files, size_t ntemps,
+ size_t nfiles, char const *outfile)
{
size_t i;
bool got_outstat = false;
@@ -1921,11 +2346,11 @@ avoid_trashing_input (char **files, size_t ntemps, size_t nfiles,
for (i = ntemps; i < nfiles; i++)
{
- bool is_stdin = STREQ (files[i], "-");
+ bool is_stdin = STREQ (files[i].name, "-");
bool same;
struct stat instat;
- if (outfile && STREQ (outfile, files[i]) && !is_stdin)
+ if (outfile && STREQ (outfile, files[i].name) && !is_stdin)
same = true;
else
{
@@ -1941,7 +2366,7 @@ avoid_trashing_input (char **files, size_t ntemps, size_t nfiles,
same = (((is_stdin
? fstat (STDIN_FILENO, &instat)
- : stat (files[i], &instat))
+ : stat (files[i].name, &instat))
== 0)
&& SAME_INODE (instat, outstat));
}
@@ -1949,9 +2374,11 @@ avoid_trashing_input (char **files, size_t ntemps, size_t nfiles,
if (same)
{
FILE *tftp;
- char *temp = create_temp_file (&tftp);
- mergefps (&files[i], 0, nfiles - i, tftp, temp);
- files[i] = temp;
+ pid_t pid;
+ char *temp = create_temp (&tftp, &pid);
+ mergefps (&files[i],0, nfiles - i, tftp, temp);
+ files[i].name = temp;
+ files[i].pid = pid;
return i + 1;
}
}
@@ -1965,7 +2392,8 @@ avoid_trashing_input (char **files, size_t ntemps, size_t nfiles,
OUTPUT_FILE; a null OUTPUT_FILE stands for standard output. */
static void
-merge (char **files, size_t ntemps, size_t nfiles, char const *output_file)
+merge (struct sortfile *files, size_t ntemps, size_t nfiles,
+ char const *output_file)
{
while (NMERGE < nfiles)
{
@@ -1986,11 +2414,13 @@ merge (char **files, size_t ntemps, size_t nfiles, char const *output_file)
for (out = in = 0; out < nfiles / NMERGE; out++, in += NMERGE)
{
FILE *tfp;
- char *temp = create_temp_file (&tfp);
+ pid_t pid;
+ char *temp = create_temp (&tfp, &pid);
size_t nt = MIN (ntemps, NMERGE);
ntemps -= nt;
mergefps (&files[in], nt, NMERGE, tfp, temp);
- files[out] = temp;
+ files[out].name = temp;
+ files[out].pid = pid;
}
remainder = nfiles - in;
@@ -2003,11 +2433,13 @@ merge (char **files, size_t ntemps, size_t nfiles, char const *output_file)
files as possible, to avoid needless I/O. */
size_t nshortmerge = remainder - cheap_slots + 1;
FILE *tfp;
- char *temp = create_temp_file (&tfp);
+ pid_t pid;
+ char *temp = create_temp (&tfp, &pid);
size_t nt = MIN (ntemps, nshortmerge);
ntemps -= nt;
mergefps (&files[in], nt, nshortmerge, tfp, temp);
- files[out++] = temp;
+ files[out].name = temp;
+ files[out++].pid = pid;
in += nshortmerge;
}
@@ -2079,7 +2511,7 @@ sort (char * const *files, size_t nfiles, char const *output_file)
else
{
++ntemps;
- temp_output = create_temp_file (&tfp);
+ temp_output = create_temp (&tfp, NULL);
}
do
@@ -2094,6 +2526,10 @@ sort (char * const *files, size_t nfiles, char const *output_file)
xfclose (tfp, temp_output);
+ /* Free up some resources every once in a while. */
+ if (MAX_PROCS_BEFORE_REAP < nprocs)
+ reap_some ();
+
if (output_file_created)
goto finish;
}
@@ -2107,10 +2543,11 @@ sort (char * const *files, size_t nfiles, char const *output_file)
{
size_t i;
struct tempnode *node = temphead;
- char **tempfiles = xnmalloc (ntemps, sizeof *tempfiles);
+ struct sortfile *tempfiles = xnmalloc (ntemps, sizeof *tempfiles);
for (i = 0; node; i++)
{
- tempfiles[i] = node->name;
+ tempfiles[i].name = node->name;
+ tempfiles[i].pid = node->pid;
node = node->next;
}
merge (tempfiles, ntemps, ntemps, output_file);
@@ -2305,7 +2742,7 @@ main (int argc, char **argv)
struct keyfield gkey;
char const *s;
int c = 0;
- bool checkonly = false;
+ char checkonly = 0;
bool mergeonly = false;
char *random_source = NULL;
bool need_random = false;
@@ -2497,8 +2934,16 @@ main (int argc, char **argv)
}
break;
+ case CHECK_OPTION:
+ c = (optarg
+ ? XARGMATCH ("--check", optarg, check_args, check_types)
+ : 'c');
+ /* Fall through. */
case 'c':
- checkonly = true;
+ case 'C':
+ if (checkonly && checkonly != c)
+ incompatible_options ("cC");
+ checkonly = c;
break;
case 'k':
@@ -2705,19 +3150,31 @@ main (int argc, char **argv)
if (checkonly)
{
if (nfiles > 1)
- error (SORT_FAILURE, 0, _("extra operand %s not allowed with -c"),
- quote (files[1]));
+ error (SORT_FAILURE, 0, _("extra operand %s not allowed with -%c"),
+ quote (files[1]), checkonly);
if (outfile)
- incompatible_options ("co");
+ {
+ static char opts[] = {0, 'o', 0};
+ opts[0] = checkonly;
+ incompatible_options (opts);
+ }
- /* POSIX requires that sort return 1 IFF invoked with -c and the
+ /* POSIX requires that sort return 1 IFF invoked with -c or -C and the
input is not properly sorted. */
- exit (check (files[0]) ? EXIT_SUCCESS : SORT_OUT_OF_ORDER);
+ exit (check (files[0], checkonly) ? EXIT_SUCCESS : SORT_OUT_OF_ORDER);
}
if (mergeonly)
- merge (files, 0, nfiles, outfile);
+ {
+ struct sortfile *sortfiles = xcalloc (nfiles, sizeof *sortfiles);
+ size_t i;
+
+ for (i = 0; i < nfiles; ++i)
+ sortfiles[i].name = files[i];
+
+ merge (sortfiles, 0, nfiles, outfile);
+ }
else
sort (files, nfiles, outfile);
diff --git a/tests/misc/Makefile.am b/tests/misc/Makefile.am
index 4ad145c4b..c4c279d67 100644
--- a/tests/misc/Makefile.am
+++ b/tests/misc/Makefile.am
@@ -1,7 +1,6 @@
# Make miscellaneous coreutils tests. -*-Makefile-*-
-# Copyright (C) 2001, 2002, 2003, 2004, 2005, 2006 Free Software
-# Foundation, Inc.
+# Copyright (C) 200-2007 Free Software Foundation, Inc.
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -22,6 +21,7 @@ EXTRA_DIST = $(TESTS)
TESTS_ENVIRONMENT = \
top_srcdir=$(top_srcdir) \
+ abs_top_builddir=$(abs_top_builddir) \
srcdir=$(srcdir) \
PACKAGE_VERSION=$(PACKAGE_VERSION) \
PERL="$(PERL)" \
@@ -69,6 +69,7 @@ TESTS = \
sha384sum \
sha512sum \
shuf \
+ sort-compress \
sort-merge \
sort-rand \
split-a \
diff --git a/tests/misc/pwd-long b/tests/misc/pwd-long
index 546550ef2..1306b327a 100755
--- a/tests/misc/pwd-long
+++ b/tests/misc/pwd-long
@@ -1,7 +1,7 @@
#!/bin/sh
# Ensure that pwd works even when run from a very deep directory.
-# Copyright (C) 2006 Free Software Foundation, Inc.
+# Copyright (C) 2006-2007 Free Software Foundation, Inc.
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -51,18 +51,39 @@ $PERL -Tw -- - <<\EOF
# Show that pwd works even when the length of the resulting
# directory name is longer than PATH_MAX.
use strict;
-use Cwd;
(my $ME = $ENV{ARGV_0}) =~ s|.*/||;
+sub normalize_to_cwd_relative ($$$)
+{
+ my ($dir, $dev, $ino) = @_;
+ my $slash = -1;
+ my $next_slash;
+ while (1)
+ {
+ $slash = index $dir, '/', $slash + 1;
+ $slash <= -1
+ and die "$ME: $dir does not contain old CWD\n";
+ my $dir_prefix = $slash ? substr ($dir, 0, $slash) : '/';
+ my ($d, $i) = (stat $dir_prefix)[0, 1];
+ $d == $dev && $i == $ino
+ and return substr $dir, $slash + 1;
+ }
+}
+
# Set up a safe, well-known environment
delete @ENV{qw(BASH_ENV CDPATH ENV PATH)};
$ENV{IFS} = '';
-my $cwd = $ENV{CWD};
+# Save CWD's device and inode numbers.
+my ($dev, $ino) = (stat '.')[0, 1];
+
+# Construct the expected "."-relative part of pwd's output.
my $z = 'z' x 31;
my $n = 256;
-my $expected = $cwd . ("/$z" x $n);
+my $expected = "/$z" x $n;
+# Remove the leading "/".
+substr ($expected, 0, 1) = '';
my $i = 0;
do
@@ -89,6 +110,15 @@ my $pwd_binary = "$build_src_dir/pwd";
-x $pwd_binary
or die "$ME: $pwd_binary is not an executable file\n";
chomp (my $actual = `$pwd_binary`);
+
+# Convert the absolute name from pwd into a $CWD-relative name.
+# This is necessary in order to avoid a spurious failure when run
+# from a directory in a bind-mounted partition. What happens is
+# pwd reads a ".." that contains two or more entries with identical
+# dev,ino that match the ones we're looking for, and it chooses a
+# name that does not correspond to the one already recorded in $CWD.
+$actual = normalize_to_cwd_relative $actual, $dev, $ino;
+
if ($expected ne $actual)
{
my $e_len = length $expected;
diff --git a/tests/misc/sort-compress b/tests/misc/sort-compress
new file mode 100755
index 000000000..af961d202
--- /dev/null
+++ b/tests/misc/sort-compress
@@ -0,0 +1,92 @@
+#!/bin/sh
+# Test use of compression by sort
+
+# Copyright (C) 2007 Free Software Foundation, Inc.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301, USA.
+
+if test "$VERBOSE" = yes; then
+ set -x
+ sort --version
+fi
+
+pwd=`pwd`
+t0=`echo "$0"|sed 's,.*/,,'`.tmp; tmp=$t0/$$
+trap 'status=$?; cd "$pwd" && chmod -R u+rwx $t0 && rm -rf $t0 && exit $status' 0
+trap '(exit $?); exit $?' 1 2 13 15
+
+framework_failure=0
+mkdir -p $tmp || framework_failure=1
+cd $tmp || framework_failure=1
+seq -w 2000 > exp || framework_failure=1
+tac exp > in || framework_failure=1
+SORT=$abs_top_builddir/src/sort
+
+if test $framework_failure = 1; then
+ echo "$0: failure in testing framework" 1>&2
+ (exit 1); exit 1
+fi
+
+fail=0
+
+# This should force the use of temp files compressed with the default gzip
+sort -S 1k in > out || fail=1
+cmp exp out || fail=1
+test $fail = 1 && diff out exp 2> /dev/null
+
+# Create our own gzip program that will be used as the default
+cat <<\EOF > gzip || fail=1
+#!/bin/sh
+tr 41 14
+touch ok
+EOF
+
+chmod +x gzip
+
+# This will find our new gzip in PATH
+PATH=.:$PATH sort -S 1k in > out || fail=1
+cmp exp out || fail=1
+test $fail = 1 && diff out exp 2> /dev/null
+test -f ok || fail=1
+rm -f ok
+
+# This is to make sure we can disable compression
+PATH=.:$PATH GNUSORT_COMPRESSOR= sort -S 1k in > out || fail=1
+cmp exp out || fail=1
+test $fail = 1 && diff out exp 2> /dev/null
+test -f ok && fail=1
+
+# This is to make sure we can use something other than gzip
+mv gzip dzip || fail=1
+GNUSORT_COMPRESSOR=./dzip sort -S 1k in > out || fail=1
+cmp exp out || fail=1
+test $fail = 1 && diff out exp 2> /dev/null
+test -f ok || fail=1
+rm -f ok
+
+# Make sure it can find other programs in PATH correctly
+PATH=.:$PATH GNUSORT_COMPRESSOR=dzip sort -S 1k in > out || fail=1
+cmp exp out || fail=1
+test $fail = 1 && diff out exp 2> /dev/null
+test -f ok || fail=1
+rm -f dzip ok
+
+# This is to make sure sort functions if it can't find the default gzip
+PATH=. "$SORT" -S 1k in > out || fail=1
+cmp exp out || fail=1
+test $fail = 1 && diff out exp 2> /dev/null
+
+(exit $fail); exit $fail
diff --git a/tests/sort/Makefile.am b/tests/sort/Makefile.am
index 2a91a5cbb..66600e5a2 100644
--- a/tests/sort/Makefile.am
+++ b/tests/sort/Makefile.am
@@ -24,44 +24,46 @@ explicit =
maint_gen = n1.I n1.X n2.I n2.X n3.I n3.X n4.I n4.X n5.I n5.X n6.I n6.X n7.I \
n7.X n8a.I n8a.X n8b.I n8b.X n9a.I n9a.X n9b.I n9b.X n10a.I n10a.X n10b.I \
n10b.X n11a.I n11a.X n11b.I n11b.X 01a.I 01a.X 02a.I 02a.X 02b.I 02b.X 02c.I \
-02c.X 02m.I 02m.X 02n.I 02n.X 02o.I 02o.X 02p.I 02p.X 03a.I 03a.X 03b.I 03b.X \
-03c.I 03c.X 03d.I 03d.X 03e.I 03e.X 03f.I 03f.X 03g.I 03g.X 03h.I 03h.X 03i.I \
-03i.X 04a.I 04a.X 04b.I 04b.X 04c.I 04c.X 04d.I 04d.X 04e.I 04e.X 05a.I 05a.X \
-05b.I 05b.X 05c.I 05c.X 05d.I 05d.X 05e.I 05e.X 05f.I 05f.X 06a.I 06a.X 06b.I \
-06b.X 06c.I 06c.X 06d.I 06d.X 06e.I 06e.X 06f.I 06f.X 07a.I 07a.X 07b.I 07b.X \
-07c.I 07c.X 07d.I 07d.X 08a.I 08a.X 08b.I 08b.X 09a.I 09a.X 09b.I 09b.X 09c.I \
-09c.X 09d.I 09d.X 10a.I 10a.X 10b.I 10b.X 10c.I 10c.X 10d.I 10d.X 10a0.I \
-10a0.X 10a1.I 10a1.X 10a2.I 10a2.X 10e.I 10e.X 10f.I 10f.X 10g.I 10g.X 11a.I \
-11a.X 11b.I 11b.X 11c.I 11c.X 11d.I 11d.X 12a.I 12a.X 12b.I 12b.X 12c.I 12c.X \
-12d.I 12d.X 13a.I 13a.X 13b.I 13b.X 14a.I 14a.X 14b.I 14b.X 15a.I 15a.X 15b.I \
-15b.X 15c.I 15c.X 15d.I 15d.X 15e.I 15e.X 16a.I 16a.X 17.I 17.X 18a.I 18a.X \
-18b.I 18b.X 18c.I 18c.X 18d.I 18d.X 18e.I 18e.X 19a.I 19a.X 19b.I 19b.X 20a.I \
-20a.X 21a.I 21a.X 21b.I 21b.X 21c.I 21c.X 21d.I 21d.X 21e.I 21e.X 21f.I 21f.X \
-21g.I 21g.X 22a.I 22a.X 22b.I 22b.X no-file1.X o-no-file1.X create-empty.X \
-neg-nls.I neg-nls.X nul-nls.I nul-nls.X use-nl.I use-nl.X o2.I o2.X \
-incompat1.I incompat1.X incompat2.I incompat2.X incompat3.I incompat3.X \
-incompat4.I incompat4.X nul-tab.I nul-tab.X bigfield.I bigfield.X
+02c.X 02d.I 02d.X 02e.I 02e.X 02m.I 02m.X 02n.I 02n.X 02o.I 02o.X 02p.I 02p.X \
+03a.I 03a.X 03b.I 03b.X 03c.I 03c.X 03d.I 03d.X 03e.I 03e.X 03f.I 03f.X 03g.I \
+03g.X 03h.I 03h.X 03i.I 03i.X 04a.I 04a.X 04b.I 04b.X 04c.I 04c.X 04d.I 04d.X \
+04e.I 04e.X 05a.I 05a.X 05b.I 05b.X 05c.I 05c.X 05d.I 05d.X 05e.I 05e.X 05f.I \
+05f.X 06a.I 06a.X 06b.I 06b.X 06c.I 06c.X 06d.I 06d.X 06e.I 06e.X 06f.I 06f.X \
+07a.I 07a.X 07b.I 07b.X 07c.I 07c.X 07d.I 07d.X 08a.I 08a.X 08b.I 08b.X 09a.I \
+09a.X 09b.I 09b.X 09c.I 09c.X 09d.I 09d.X 10a.I 10a.X 10b.I 10b.X 10c.I 10c.X \
+10d.I 10d.X 10a0.I 10a0.X 10a1.I 10a1.X 10a2.I 10a2.X 10e.I 10e.X 10f.I 10f.X \
+10g.I 10g.X 11a.I 11a.X 11b.I 11b.X 11c.I 11c.X 11d.I 11d.X 12a.I 12a.X 12b.I \
+12b.X 12c.I 12c.X 12d.I 12d.X 13a.I 13a.X 13b.I 13b.X 14a.I 14a.X 14b.I 14b.X \
+15a.I 15a.X 15b.I 15b.X 15c.I 15c.X 15d.I 15d.X 15e.I 15e.X 16a.I 16a.X 17.I \
+17.X 18a.I 18a.X 18b.I 18b.X 18c.I 18c.X 18d.I 18d.X 18e.I 18e.X 19a.I 19a.X \
+19b.I 19b.X 20a.I 20a.X 21a.I 21a.X 21b.I 21b.X 21c.I 21c.X 21d.I 21d.X 21e.I \
+21e.X 21f.I 21f.X 21g.I 21g.X 22a.I 22a.X 22b.I 22b.X no-file1.X o-no-file1.X \
+create-empty.X neg-nls.I neg-nls.X nul-nls.I nul-nls.X use-nl.I use-nl.X o2.I \
+o2.X incompat1.I incompat1.X incompat2.I incompat2.X incompat3.I incompat3.X \
+incompat4.I incompat4.X incompat5.I incompat5.X incompat6.I incompat6.X \
+nul-tab.I nul-tab.X bigfield.I bigfield.X
run_gen = n1.O n1.E n2.O n2.E n3.O n3.E n4.O n4.E n5.O n5.E n6.O n6.E n7.O \
n7.E n8a.O n8a.E n8b.O n8b.E n9a.O n9a.E n9b.O n9b.E n10a.O n10a.E n10b.O \
n10b.E n11a.O n11a.E n11b.O n11b.E 01a.O 01a.E 02a.O 02a.E 02b.O 02b.E 02c.O \
-02c.E 02m.O 02m.E 02n.O 02n.E 02o.O 02o.E 02p.O 02p.E 03a.O 03a.E 03b.O 03b.E \
-03c.O 03c.E 03d.O 03d.E 03e.O 03e.E 03f.O 03f.E 03g.O 03g.E 03h.O 03h.E 03i.O \
-03i.E 04a.O 04a.E 04b.O 04b.E 04c.O 04c.E 04d.O 04d.E 04e.O 04e.E 05a.O 05a.E \
-05b.O 05b.E 05c.O 05c.E 05d.O 05d.E 05e.O 05e.E 05f.O 05f.E 06a.O 06a.E 06b.O \
-06b.E 06c.O 06c.E 06d.O 06d.E 06e.O 06e.E 06f.O 06f.E 07a.O 07a.E 07b.O 07b.E \
-07c.O 07c.E 07d.O 07d.E 08a.O 08a.E 08b.O 08b.E 09a.O 09a.E 09b.O 09b.E 09c.O \
-09c.E 09d.O 09d.E 10a.O 10a.E 10b.O 10b.E 10c.O 10c.E 10d.O 10d.E 10a0.O \
-10a0.E 10a1.O 10a1.E 10a2.O 10a2.E 10e.O 10e.E 10f.O 10f.E 10g.O 10g.E 11a.O \
-11a.E 11b.O 11b.E 11c.O 11c.E 11d.O 11d.E 12a.O 12a.E 12b.O 12b.E 12c.O 12c.E \
-12d.O 12d.E 13a.O 13a.E 13b.O 13b.E 14a.O 14a.E 14b.O 14b.E 15a.O 15a.E 15b.O \
-15b.E 15c.O 15c.E 15d.O 15d.E 15e.O 15e.E 16a.O 16a.E 17.O 17.E 18a.O 18a.E \
-18b.O 18b.E 18c.O 18c.E 18d.O 18d.E 18e.O 18e.E 19a.O 19a.E 19b.O 19b.E 20a.O \
-20a.E 21a.O 21a.E 21b.O 21b.E 21c.O 21c.E 21d.O 21d.E 21e.O 21e.E 21f.O 21f.E \
-21g.O 21g.E 22a.O 22a.E 22b.O 22b.E no-file1.O no-file1.E o-no-file1.O \
-o-no-file1.E create-empty.O create-empty.E neg-nls.O neg-nls.E nul-nls.O \
-nul-nls.E use-nl.O use-nl.E o2.O o2.E incompat1.O incompat1.E incompat2.O \
-incompat2.E incompat3.O incompat3.E incompat4.O incompat4.E nul-tab.O \
-nul-tab.E bigfield.O bigfield.E
+02c.E 02d.O 02d.E 02e.O 02e.E 02m.O 02m.E 02n.O 02n.E 02o.O 02o.E 02p.O 02p.E \
+03a.O 03a.E 03b.O 03b.E 03c.O 03c.E 03d.O 03d.E 03e.O 03e.E 03f.O 03f.E 03g.O \
+03g.E 03h.O 03h.E 03i.O 03i.E 04a.O 04a.E 04b.O 04b.E 04c.O 04c.E 04d.O 04d.E \
+04e.O 04e.E 05a.O 05a.E 05b.O 05b.E 05c.O 05c.E 05d.O 05d.E 05e.O 05e.E 05f.O \
+05f.E 06a.O 06a.E 06b.O 06b.E 06c.O 06c.E 06d.O 06d.E 06e.O 06e.E 06f.O 06f.E \
+07a.O 07a.E 07b.O 07b.E 07c.O 07c.E 07d.O 07d.E 08a.O 08a.E 08b.O 08b.E 09a.O \
+09a.E 09b.O 09b.E 09c.O 09c.E 09d.O 09d.E 10a.O 10a.E 10b.O 10b.E 10c.O 10c.E \
+10d.O 10d.E 10a0.O 10a0.E 10a1.O 10a1.E 10a2.O 10a2.E 10e.O 10e.E 10f.O 10f.E \
+10g.O 10g.E 11a.O 11a.E 11b.O 11b.E 11c.O 11c.E 11d.O 11d.E 12a.O 12a.E 12b.O \
+12b.E 12c.O 12c.E 12d.O 12d.E 13a.O 13a.E 13b.O 13b.E 14a.O 14a.E 14b.O 14b.E \
+15a.O 15a.E 15b.O 15b.E 15c.O 15c.E 15d.O 15d.E 15e.O 15e.E 16a.O 16a.E 17.O \
+17.E 18a.O 18a.E 18b.O 18b.E 18c.O 18c.E 18d.O 18d.E 18e.O 18e.E 19a.O 19a.E \
+19b.O 19b.E 20a.O 20a.E 21a.O 21a.E 21b.O 21b.E 21c.O 21c.E 21d.O 21d.E 21e.O \
+21e.E 21f.O 21f.E 21g.O 21g.E 22a.O 22a.E 22b.O 22b.E no-file1.O no-file1.E \
+o-no-file1.O o-no-file1.E create-empty.O create-empty.E neg-nls.O neg-nls.E \
+nul-nls.O nul-nls.E use-nl.O use-nl.E o2.O o2.E incompat1.O incompat1.E \
+incompat2.O incompat2.E incompat3.O incompat3.E incompat4.O incompat4.E \
+incompat5.O incompat5.E incompat6.O incompat6.E nul-tab.O nul-tab.E \
+bigfield.O bigfield.E
##test-files-end
EXTRA_DIST = Test.pm $x-tests $(explicit) $(maint_gen)
diff --git a/tests/sort/Test.pm b/tests/sort/Test.pm
index 6bed61b7f..dc41d9212 100755
--- a/tests/sort/Test.pm
+++ b/tests/sort/Test.pm
@@ -51,6 +51,8 @@ my @tv = (
["02a", '-c', "A\nB\nC\n", '', 0],
["02b", '-c', "A\nC\nB\n", '', 1],
["02c", '-c -k1,1', "a\na b\n", '', 0],
+["02d", '-C', "A\nB\nC\n", '', 0],
+["02e", '-C', "A\nC\nB\n", '', 1],
# This should fail because there are duplicate keys
["02m", '-cu', "A\nA\n", '', 1],
["02n", '-cu', "A\nB\n", '', 0],
@@ -272,6 +274,8 @@ my @tv = (
["incompat2", '-fR', '', '', 2],
["incompat3", '-dfgiMnR', '', '', 2],
["incompat4", '-c -o /dev/null', '', '', 2],
+["incompat5", '-C -o /dev/null', '', '', 2],
+["incompat6", '-cC', '', '', 2],
# -t '\0' is accepted, as of coreutils-5.0.91
['nul-tab', "-k2,2 -t '\\0'", "a\0z\01\nb\0y\02\n", "b\0y\02\na\0z\01\n", 0],