diff options
-rw-r--r-- | ChangeLog | 129 | ||||
-rw-r--r-- | Makefile.am | 3 | ||||
-rw-r--r-- | Makefile.maint | 2 | ||||
-rw-r--r-- | NEWS | 13 | ||||
-rw-r--r-- | bootstrap.conf | 3 | ||||
-rw-r--r-- | configure.ac | 2 | ||||
-rw-r--r-- | doc/coreutils.texi | 35 | ||||
-rw-r--r-- | lib/.cvsignore | 4 | ||||
-rw-r--r-- | lib/.gitignore | 4 | ||||
-rw-r--r-- | m4/.cvsignore | 2 | ||||
-rw-r--r-- | m4/.gitignore | 2 | ||||
-rw-r--r-- | src/sort.c | 591 | ||||
-rw-r--r-- | tests/misc/Makefile.am | 5 | ||||
-rwxr-xr-x | tests/misc/pwd-long | 38 | ||||
-rwxr-xr-x | tests/misc/sort-compress | 92 | ||||
-rw-r--r-- | tests/sort/Makefile.am | 72 | ||||
-rwxr-xr-x | tests/sort/Test.pm | 4 |
17 files changed, 885 insertions, 116 deletions
@@ -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; } || : @@ -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], |