]> git.ipfire.org Git - thirdparty/coreutils.git/commitdiff
merge from trunk
authorJim Meyering <jim@meyering.net>
Fri, 26 Jan 2007 10:15:31 +0000 (11:15 +0100)
committerJim Meyering <jim@meyering.net>
Fri, 26 Jan 2007 10:15:31 +0000 (11:15 +0100)
17 files changed:
ChangeLog
Makefile.am
Makefile.maint
NEWS
bootstrap.conf
configure.ac
doc/coreutils.texi
lib/.cvsignore
lib/.gitignore
m4/.cvsignore
m4/.gitignore
src/sort.c
tests/misc/Makefile.am
tests/misc/pwd-long
tests/misc/sort-compress [new file with mode: 0755]
tests/sort/Makefile.am
tests/sort/Test.pm

index 70ca3ec79ca75c7cdf6047972a9397a881e9e80d..09b7dbb4a4476faa39fdf178890ee5a82527429e 100644 (file)
--- 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.
index 9eeeb2037d18fc36d66b8505c341f0e7af712d68..1a6307717f42ba9e2f7c6d6bb2320d9bb04478bc 100644 (file)
@@ -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
index 9baafa3d8d3f2face81b88000edefd14058b91c8..d526baacda6edeebc4122d135a3a17c403162262 100644 (file)
@@ -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 a30b17e52fed2740d41f286cf44f09eb57072673..ad0fd1d242417600af00cd257a1f028ab7d55253 100644 (file)
--- 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]
 
index 73e7d4926d3364bbb4c0acf97d54cd467af2e376..b262622e0a625cab446f7b3b45793d2f2689ddba 100644 (file)
@@ -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
index 25b23b5979634c5bd7feb6984180d17f8708af85..66f079b408aed7bda9e1e3006586b32919c352fa 100644 (file)
@@ -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")
index 89e97d8bc21f458fb0a750dcd8c60705729deec0..ff049bcf760add546da1a350243f8289d54a28b5 100644 (file)
@@ -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.
index fb5f9e0ea05fb32f080588b5cb1566434719c95f..28dc243ee7fbb4384f91292da75006a5e308321f 100644 (file)
@@ -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
index 1ebe1d47defe08703e08f1d40cdf68f1dcee28de..873c4ce7a7eae00f0308f4a07fbd209a7506d551 100644 (file)
@@ -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
index 0311ce8c1f765158d252bdf96400542198b263e1..74c32371b3b809eb77fd21237b2c6619a6f69ee7 100644 (file)
@@ -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
index e6ae77991f59983def972002d44a7f7ecebafbe5..1916f1862dc2f2c06ad77a7512fc90c2daccfb1a 100644 (file)
@@ -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
index 8a2279637202c1dd94d63df63efce51589bc8c88..c7ae0c8c0f884aa08921af9d42540fb7be8e5549 100644 (file)
@@ -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
 
 #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
@@ -533,6 +762,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)
 {
@@ -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);
 
index 4ad145c4bcee87bdb2bb234325585fbbb21d2779..c4c279d6746739e7888316867af25804c5f97427 100644 (file)
@@ -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 \
index 546550ef2d71fe5d0c504b882f4e3154322e3831..1306b327a1b135b10d767a4487e3df6f9196c1d5 100755 (executable)
@@ -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 (executable)
index 0000000..af961d2
--- /dev/null
@@ -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
index 2a91a5cbb7be09dbf02cff58ed400fc5c6842746..66600e5a2e8884a3034708c1e8bf4e0ab9dafa3f 100644 (file)
@@ -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)
index 6bed61b7ff0481e4a7a235fec65dc210994bf185..dc41d9212cde376b26ff0f2a3b2ee0e72e65c32a 100755 (executable)
@@ -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],