From 42e176d01982f038f12859880c530d3a49a5f4ac Mon Sep 17 00:00:00 2001 From: Bo Borgerson Date: Sun, 6 Apr 2008 17:54:08 -0400 Subject: [PATCH] Add new program: zargs * AUTHORS: Register as the author. * NEWS: Advertise new program. * README: List new program. * doc/coreutils.texi: Describe new program. * man/Makefile.am: Add new program. * man/zargs.x: Add new man page template. * po/POTFILES.in: Add new program. * src/Makefile.am: Add new program. * src/zargs.c: Add new program. * tests/misc/Makefile.am: Add new test. * tests/misc/help-version: Accomodate new program. * tests/misc/zargs: Test new program. Signed-off-by: Bo Borgerson --- AUTHORS | 1 + NEWS | 3 + README | 1 + doc/coreutils.texi | 74 ++++- man/Makefile.am | 1 + man/zargs.x | 4 + po/POTFILES.in | 1 + src/Makefile.am | 2 +- src/zargs.c | 910 +++++++++++++++++++++++++++++++++++++++++++++++ tests/misc/Makefile.am | 3 +- tests/misc/help-version | 1 + tests/misc/zargs | 82 +++++ 12 files changed, 1079 insertions(+), 4 deletions(-) create mode 100644 man/zargs.x create mode 100644 src/zargs.c create mode 100755 tests/misc/zargs diff --git a/AUTHORS b/AUTHORS index 807857f..5c1c6d1 100644 --- a/AUTHORS +++ b/AUTHORS @@ -100,3 +100,4 @@ wc: Paul Rubin, David MacKenzie who: Joseph Arceneaux, David MacKenzie, Michael Stone whoami: Richard Mlynarik yes: David MacKenzie +zargs: Bo Borgerson diff --git a/NEWS b/NEWS index e208b30..69f548f 100644 --- a/NEWS +++ b/NEWS @@ -82,6 +82,9 @@ GNU coreutils NEWS -*- outline -*- Fix a non-portable use of sed in configure.ac. [bug introduced in coreutils-6.9.92] +** New programs + +zargs: run a program with automatically decompressed inputs * Noteworthy changes in release 6.9.92 (2008-01-12) [beta] diff --git a/README b/README index 7a608f4..4092411 100644 --- a/README +++ b/README @@ -15,6 +15,7 @@ The programs that can be built with this package are: runcon seq sha1sum sha224sum sha256sum sha384sum sha512sum shred shuf sleep sort split stat stty su sum sync tac tail tee test touch tr true tsort tty uname unexpand uniq unlink uptime users vdir wc who whoami yes + zargs See the file NEWS for a list of major changes in the current release. diff --git a/doc/coreutils.texi b/doc/coreutils.texi index 5a6f2c3..348ec30 100644 --- a/doc/coreutils.texi +++ b/doc/coreutils.texi @@ -126,6 +126,7 @@ * who: (coreutils)who invocation. Print who is logged in. * whoami: (coreutils)whoami invocation. Print effective user ID. * yes: (coreutils)yes invocation. Print a string indefinitely. +* zargs: (coreutils)zargs invocation. Run with decompressed inputs. @end direntry @copying @@ -190,7 +191,7 @@ Free Documentation License''. * Working context:: pwd stty printenv tty * User information:: id logname whoami groups users who * System context:: date uname hostname hostid -* Modified command invocation:: chroot env nice nohup su +* Modified command invocation:: chroot env nice nohup su zargs * Process control:: kill * Delaying:: sleep * Numeric operations:: factor seq @@ -421,6 +422,7 @@ Modified command invocation * nice invocation:: Run a command with modified niceness * nohup invocation:: Run a command immune to hangups * su invocation:: Run a command with substitute user and group ID +* zargs invocation:: Run a command with decompressed inputs Process control @@ -680,7 +682,8 @@ meanings with the values @samp{0} and @samp{1}. Here are some of the exceptions: @command{chroot}, @command{env}, @command{expr}, @command{nice}, @command{nohup}, @command{printenv}, address@hidden, @command{su}, @command{test}, @command{tty}. address@hidden, @command{su}, @command{test}, @command{tty}, address@hidden @node Backup options @@ -13358,6 +13361,7 @@ user, etc. * nice invocation:: Modify niceness. * nohup invocation:: Immunize to hangups. * su invocation:: Modify user and group ID. +* zargs invocation:: Pipe compressed inputs through decompressors. @end menu @@ -13509,6 +13513,72 @@ Exit status: the exit status of @var{command} otherwise @end display address@hidden zargs invocation address@hidden @command{zargs}: Run a command with decompressed inputs. + address@hidden zargs address@hidden fifos, piped inputs, run a command with decompressed address@hidden decompression, automatic decompression + address@hidden runs a command with a modified environment. +Synopsis: + address@hidden +zargs address@hidden@dots{} @var{command} @var{arg} address@hidden@dots{} address@hidden example + address@hidden to @var{command} are examined. For any @var{arg} that is a +regular file that appears to be compressed, a fifo is created and attached +to an appropriate decompression program. That @var{arg} is replaced with +the name of the fifo. +All temporary fifos are cleaned up after @var{command} finishes. + +The program accepts the following options. Also see @ref{Common options}. +Options must precede operands. + address@hidden @samp + address@hidden -T @var{dir} address@hidden address@hidden address@hidden -T address@hidden --temporary-directory +put temporary directories in @var{dir}, instead of @var{$TMPDIR} or /tmp. + address@hidden -M @var{MAX} address@hidden address@hidden address@hidden -M address@hidden --max-directory-entries +Put at most @var{max} fifos in any directory. A new temporary directory +will be created for each @var{max} fifos. + address@hidden table + +The program also supports the following options to @var{command}. + address@hidden @samp + address@hidden@var{command}, } + +This option is modified and passed through to @var{command}. @var{arg}s +received from @var{file} are interpreted and modified as those passed on +the command-line. + address@hidden address@hidden address@hidden --zargs-skip +Pass @var{arg} through unmodified (even if it is the name of a compressed +file). The option prefix is removed before @var{command} is invoked. + address@hidden table + + address@hidden exit status of @command{zargs} +Exit status: + address@hidden +1 if @command{zargs} initialization fails +the exit status of @var{command} otherwise address@hidden display + @node nice invocation @section @command{nice}: Run a command with modified niceness diff --git a/man/Makefile.am b/man/Makefile.am index 7164b3b..aa993a6 100644 --- a/man/Makefile.am +++ b/man/Makefile.am @@ -129,6 +129,7 @@ wc.1: $(common_dep) $(srcdir)/wc.x ../src/wc.c who.1: $(common_dep) $(srcdir)/who.x ../src/who.c whoami.1: $(common_dep) $(srcdir)/whoami.x ../src/whoami.c yes.1: $(common_dep) $(srcdir)/yes.x ../src/yes.c +zargs.1: $(common_dep) $(srcdir)/zargs.x ../src/zargs.c SUFFIXES = .x .1 diff --git a/man/zargs.x b/man/zargs.x new file mode 100644 index 0000000..20ee35f --- /dev/null +++ b/man/zargs.x @@ -0,0 +1,4 @@ +[NAME] +zargs \- open compressed inputs through decompressors. +[DESCRIPTION] +.\" Add any additional description here diff --git a/po/POTFILES.in b/po/POTFILES.in index e975109..5ee00a2 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -129,3 +129,4 @@ src/wc.c src/who.c src/whoami.c src/yes.c +src/zargs.c diff --git a/src/Makefile.am b/src/Makefile.am index 44d802e..d128f2c 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -39,7 +39,7 @@ EXTRA_PROGRAMS = \ basename date dirname echo env expr factor false \ id kill logname pathchk printenv printf pwd \ runcon seq sleep tee \ - test true tty whoami yes \ + test true tty whoami yes zargs \ base64 bin_PROGRAMS = $(OPTIONAL_BIN_PROGS) diff --git a/src/zargs.c b/src/zargs.c new file mode 100644 index 0000000..0d18795 --- /dev/null +++ b/src/zargs.c @@ -0,0 +1,910 @@ +/* zargs -- read from compressed files and pipelines + Copyright (C) 2008 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 3 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, see . */ + + +/* zargs - run a program with multiple piped inputs + + Written by Bo Borgerson. */ + +#include +#include +#include +#include +#include +#include +#include "system.h" +#include "error.h" +#include "long-options.h" +#include "readtokens0.h" +#include "quote.h" +#include "xstrtol.h" + +#define PROGRAM_NAME "zargs" + +#define AUTHORS "Bo Borgerson" + +#define GZIP_MAGIC "\037\213" /* Magic for gzip files. */ +#define GZIP_MAGIC_OLD "\037\236" /* Magic for old gzip files. */ +#define BZIP_MAGIC "\102\132" /* Magic for bzip files. */ + +#ifndef DEFAULT_TMPDIR +# define DEFAULT_TMPDIR "/tmp" +#endif + +/* Use SA_NOCLDSTOP as a proxy for whether the sigaction machinery is + present. */ +#ifndef SA_NOCLDSTOP +# define SA_NOCLDSTOP 0 +/* No sigprocmask. Always 'return' zero. */ +# define sigprocmask(How, Set, Oset) (0) +# define sigset_t int +# if ! HAVE_SIGINTERRUPT +# define siginterrupt(sig, flag) /* empty */ +# endif +#endif + +#define FILES_FROM_OPT "--files0-from=" +#define FILES_FROM_OPT_LEN strlen (FILES_FROM_OPT); + +/* By default we won't put more than this many fifos in a single directory. + Can be overridden on the command-line with the --max-directory-entries + (-M) option. */ +#define DEFAULT_MAX_DIRENTS 128 + +#define SECONDS_BETWEEN_REAPS 4 + +static char *program_name; + +/* ARGs prefixed with this string will be left alone + (the string is stripped). */ +static char *skip_opt = "--zargs-skip="; + +static struct option const long_options[] = { + {"temporary-directory", required_argument, NULL, 'T'}, + {"max-directory-entries", required_argument, NULL, 'M'}, + {GETOPT_HELP_OPTION_DECL}, + {GETOPT_VERSION_OPTION_DECL}, + {NULL, 0, NULL, 0} +}; + +static void +usage (int status) +{ + if (status != EXIT_SUCCESS) + fprintf (stderr, _("Try `%s --help' for more information.\n"), + program_name); + else + { + printf (_("\ +Usage: %s [OPTION]... COMMAND ARG [ARG]...\n\ + Or: %s [OPTION]... COMMAND [OPTION]... --files0-from=F\n\ +"), program_name, program_name); + + fputs (_("\ +Examine the ARGs to COMMAND. For any ARG that is a regular file that\n\ +appears to be compressed, create a fifo, attach an appropriate\n\ +decompression program and replace the argument with the name of the fifo.\n\ +"), stdout); + fputs (_("\ +Leave any ARG that appears to be an option as-is. Clean up all the\n\ +temporary fifos after COMMAND finishes.\n\ +\n\ +Options (must come before COMMAND):\n\ +"), stdout); + + printf (_("\ +\n\ + -T, --temporary-directory=DIR put temporary directories in DIR,\n\ + not $TMPDIR or %s;\n\ + -M, --max-directory-entries=MAX put at most MAX fifos in any directory;\n\ + create a new directory after MAX\n\ +"), DEFAULT_TMPDIR); + fputs (HELP_OPTION_DESCRIPTION, stdout); + fputs (VERSION_OPTION_DESCRIPTION, stdout); + fputs (_("\ +\n\ +Interpreted options to COMMAND:\n\ +\n\ +"), stdout); + fputs (_("\ + --zargs-skip=ARG pass this ARG through unaffected\n\ + --files0-from=F read additional ARGs from F and pipe the modified\n\ + set to COMMAND through a fifo; F is replaced with\n\ + the name of the fifo.\n\ +"), stdout); + emit_bug_reporting_address (); + } + exit (status); +} + +/* The set of signals we need to look out for. */ +static int const all_sig[] = +{ + /* The usual suspects, minus ALRM, which is used for reaping. */ + SIGHUP, SIGINT, SIGPIPE, SIGQUIT, SIGTERM, +#ifdef SIGPOLL + SIGPOLL, +#endif +#ifdef SIGPROF + SIGPROF, +#endif +#ifdef SIGVTALRM + SIGVTALRM, +#endif +#ifdef SIGXCPU + SIGXCPU, +#endif +#ifdef SIGXFSZ + SIGXFSZ, +#endif +}; + +enum { nsigs = sizeof all_sig / sizeof all_sig[0] }; + +/* The total number of forked processes that have not been reaped yet. */ +static size_t nprocs; + +/* The PID of our main child (the one who will be reading from our fifos). */ +static int command_pid; + +/* The exit status of our main child. */ +static int command_status; + +/* A list of the names of our fifos (so we can unlink them later). */ +static char **fifos; + +/* How many fifos are in our list. */ +static size_t fifo_count; + +/* How many fifos we've got space allocated for currently. */ +static size_t fifo_alloc; + +/* A list of temporary directories containing our fifos. */ +static char **fifo_dirs; + +/* How many temporary directories we have in our list. */ +static size_t fifo_dir_count = 0; + +/* How many directories we have space allocated for currently. */ +static size_t fifo_dir_alloc = 0; + +/* Directory where we create our temporary directories. */ +static char *fifo_dir_base; + +/* How many fifos we've put into the last temporary directory we created. */ +static size_t cur_fifo_dir_fifos = 0; + +/* How many fifos we'll put into a directory before creating a new one. + Can be set on the command-line with --max-directory-entries (-M). */ +static size_t max_fifo_dir_fifos = DEFAULT_MAX_DIRENTS; + +/* If our COMMAND uses --files0-from=F, then we need to take ARGs from that + file, and transform the --files0-from=F option to point to a fifo we'll + write to. */ +static char *files_from = NULL; + +/* The length first of the prefix "--files0-from=", then of our full + replacement */ +static size_t files_from_opt_len; + +/* The string containing first the prefix "--files0-from", then our + full replacement. */ +static char *files_from_opt; + +/* The index in the ARG list of the --files0-from option. */ +static int files_from_opt_i = 0; + +/* 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); + } +} + +/* Clean up after ourselves. First unlink fifos, then rmdir directories. + Free memory, even though we currently only clean up at exit. */ + +static void +cleanup (void) +{ + int i; + + for (i = 0; i < fifo_count; i++) + { + unlink (fifos[i]); + free (fifos[i]); + } + if (fifos) + { + free (fifos); + fifos = NULL; + } + for (i = 0; i < fifo_dir_count; i++) + { + if(rmdir (fifo_dirs[i]) == -1) + error (0, errno, _("rmdir failed: %s"), fifo_dirs[i]); + free (fifo_dirs[i]); + fifo_dirs[i] = NULL; + } + if (fifo_dirs) + { + free (fifo_dirs); + fifo_dirs = NULL; + } + if (fifo_dir_base) + { + free (fifo_dir_base); + fifo_dir_base = NULL; + } +} + +/* Cleanup actions to take when exiting. */ + +static void +exit_cleanup (void) +{ + /* Clean up in a critical section so that a signal handler does not + try to clean up too. */ + struct cs_status cs = cs_enter (); + cleanup (); + cs_leave (cs); + + close_stdout (); +} + +/* Handle interrupts and hangups. */ + + +static void +sighandler (int sig) +{ + size_t i; + static int n_sig = 0; + if (! SA_NOCLDSTOP) + signal (sig, SIG_IGN); + + cleanup (); + + for (i = 0; i < (1<<30); i++); + + signal (sig, SIG_DFL); + raise (sig); +} + +/* Wrapper around dup2 for convenience. */ + +static void +dup2_or_die (int oldfd, int newfd) +{ + if (dup2 (oldfd, newfd) < 0) + error (EXIT_FAILURE, errno, _("dup2 failed")); +} + +/* 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. + 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 (EXIT_FAILURE, errno, _("waitpid")); + + else if (0 < cpid) + { + if (! WIFEXITED (status) || WEXITSTATUS (status)) + error (0, 0, _("%s terminated abnormally"), + (cpid == command_pid)?"command":"child"); + if (cpid == command_pid) + command_status = status; + else + --nprocs; + } + + return cpid; +} + +/* 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. This is the sigaction handler for ALRM. + It reschedules itself for a second later after it's done. */ + +static void +reap_some (int sig) +{ + pid_t pid; + + while (0 < nprocs && (pid = reap (-1))); + + if (nprocs) + alarm (SECONDS_BETWEEN_REAPS); +} + +/* Fork a child process. Don't let the child know where the fifos + and temporary directories are. We want to clean them up ourselves. + Return the PID of the child or -1 if fork failed. */ + +static pid_t +safe_fork (void) +{ +#if HAVE_WORKING_FORK + char **saved_fifos; + int saved_errno; + int i; + unsigned int wait_retry = 1; + pid_t pid IF_LINT (= -1); + struct cs_status cs; + + + /* This is so the child process won't delete our temp files + if it receives a signal before exec-ing. */ + cs = cs_enter (); + saved_fifos = fifos; + fifos = NULL; + + pid = fork (); + saved_errno = errno; + if (pid) + fifos = saved_fifos; + + if (0 == pid) + for (i = 0; i < nsigs; i++) + signal (all_sig[i], SIG_DFL); + + cs_leave (cs); + errno = saved_errno; + + if (0 < pid) + ++nprocs; + + return pid; + +#else /* ! HAVE_WORKING_FORK */ + return -1; +#endif +} + +/* Create a temporary directory into which we'll put fifos. */ + +static void +create_fifo_dir (void) +{ + static char const slashbase[] = "/zargsXXXXXX"; + struct cs_status cs; + int saved_errno; + size_t len = strlen (fifo_dir_base); + char *success; + char *fifo_dir; + + if (fifo_dir_count == fifo_dir_alloc) + fifo_dirs = X2NREALLOC (fifo_dirs, &fifo_dir_alloc); + + /* One extra for the trailing slash */ + fifo_dirs[fifo_dir_count] = xmalloc (len + sizeof slashbase + 1); + + fifo_dir = fifo_dirs[fifo_dir_count++]; + + memcpy (fifo_dir, fifo_dir_base, len); + memcpy (fifo_dir + len, slashbase, sizeof slashbase); + + + /* Create the directory in a critical section, to avoid races. */ + cs = cs_enter (); + success = mkdtemp (fifo_dir); + saved_errno = errno; + cs_leave (cs); + errno = saved_errno; + + if (!success) + { + /* Note that the last FIFO_DIR doesn't get freed, but we're exiting + anyway. */ + fifo_dir_count--; + error (EXIT_FAILURE, errno, + _("cannot create temporary directory: %s"), fifo_dir); + } + + /* Append a slash so we don't have to worry about it later. */ + fifo_dir[len + sizeof slashbase - 1] = '/'; + fifo_dir[len + sizeof slashbase] = '\0'; + + cur_fifo_dir_fifos = 0; +} + +/* Use DIR as our base temp dir. */ +static void +set_temp_dir (char const *dir) +{ + size_t len; + if (fifo_dir_base) + { + if (!STREQ (fifo_dir_base, dir)) + error (EXIT_FAILURE, 0, _("multiple temp dirs specified")); + } + else + { + len = strlen (dir); + fifo_dir_base = xmalloc (len + 1); + memcpy (fifo_dir_base, dir, len); + } +} + +/* Create a new fifo with a name resembling the file or command it's + associated with. */ +static char * +new_fifo (char const *file_name) +{ + char *fifo_tag = strrchr (file_name, '/'); + size_t len1, len2, len3 = 0; + size_t len3_alloc = INT_BUFSIZE_BOUND (unsigned int) + 1; + char i_str [INT_BUFSIZE_BOUND (unsigned int) + 1]; + unsigned int i = 0; + int still_trying = 1; + char *fifo; + char *fifo_dir; + + if (fifo_tag) + fifo_tag++; + else + fifo_tag = (char *)file_name; + + if (fifo_count == fifo_alloc) + fifos = X2NREALLOC (fifos, &fifo_alloc); + + if (!fifo_dir_count || cur_fifo_dir_fifos == max_fifo_dir_fifos) + create_fifo_dir (); + + cur_fifo_dir_fifos++; + + fifo_dir = fifo_dirs[fifo_dir_count - 1]; + + len1 = strlen (fifo_dir); + len2 = strlen (fifo_tag); + + fifos[fifo_count] = xmalloc (len1 + len2 + len3_alloc); + + fifo = fifos[fifo_count++]; + + memcpy (fifo, fifo_dir, len1); + memcpy (fifo + len1, fifo_tag, len2 + 1); + + /* There may be files with the same name in different directories, so + we need to handle collisions. This gets inefficient as we get more + identical filenames. */ + while (still_trying && (i < UINT_MAX)) + { + + if (i) + { + sprintf (i_str, "-%d", i); + len3 = strlen (i_str); + memcpy (fifo + len1 + len2, i_str, len3 + 1); + } + + if ((still_trying = mkfifo (fifo, 0600)) != 0) + { + if (errno != EEXIST) + error (EXIT_FAILURE, errno, _("cannot create fifo %s"), + quote (fifo)); + } + + i++; + } + + return fifo; +} + +/* See if this ARG matches a filename. If it does, and it's a regular file, + see if its magic corresponds to a compression format and set up for + auto-decompression. Returns the filename if it's a file, but non-regular + or not compressed, the fifo name if compressed or NULL if not a file. */ +static char * +handle_file (int i, char *p) +{ + int filefd, pipefd; + + struct stat st; + + char magic[2] = {0,0}; + char *open_prog = NULL; + + pid_t helper_pid; + + + if (stat (p, &st) == 0) + { + if (S_ISREG (st.st_mode)) + { + + if ((filefd = open (p, O_RDONLY)) == -1) + error (EXIT_FAILURE, errno, _("open failed: %s"), p); + + if ((read (filefd, &magic, 2)) == -1) + error (EXIT_FAILURE, errno, _("read failed: %s"), p); + + + if (!memcmp (magic, GZIP_MAGIC, 2) + || !memcmp (magic, GZIP_MAGIC_OLD, 2)) + open_prog = "gzip"; + + else if (!memcmp (magic, BZIP_MAGIC, 2)) + open_prog = "bzip2"; + + if (open_prog) + { + char *fifo; + + lseek (filefd, 0, SEEK_SET); + + fifo = new_fifo (p); + + helper_pid = safe_fork (); + + if (helper_pid > 0) + { + close (filefd); + + return fifo; + + } + else if (helper_pid == 0) + { + + close (STDIN_FILENO); + close (STDOUT_FILENO); + + /* This will block. */ + if ((pipefd = open (fifo, O_WRONLY)) == -1) + error (EXIT_FAILURE, errno, _("open failed: %s"), + fifo); + + dup2_or_die (pipefd, STDOUT_FILENO); + dup2_or_die (filefd, STDIN_FILENO); + + execlp (open_prog, open_prog, "-c", "-f", "-d", NULL); + + error (EXIT_FAILURE, errno, _("execlp failed: %s"), + open_prog); + + } + else + error (EXIT_FAILURE, errno, _("fork failed: %s"), + open_prog); + + } + + } + return p; /* It's a file, at least */ + } + + return NULL; +} + +/* If this ARG looks like an option, return it. Otherwise, return NULL. + Also, if it looks like a --files0-from option, set up for files_from + processing. */ +static char * +handle_option (int i, char *p) +{ + + static size_t skip_opt_len = -1; + + if (skip_opt_len == -1) + skip_opt_len = strlen (skip_opt); + + if (!strncmp (p, files_from_opt, files_from_opt_len)) + { + files_from = p + files_from_opt_len; + files_from_opt_i = i; + return p; + } + else if (!strncmp (p, skip_opt, skip_opt_len)) + return p + skip_opt_len; + else if (*p == '-') + return p; + + return NULL; +} + + +/* Cascade through ARG handlers in order of precedence. Return the new + value to go in ARG's place. */ +static char * +handle_arg (int i, char *p) +{ + char *retval; + + if ((retval = handle_option (i, p)) != NULL) + return retval; + + if ((retval = handle_file (i, p)) != NULL) + return retval; + + return p; +} + +/* Specify the maximum number of fifos we'll create in a single directory + before creating a new temporary directory. */ +static void +specify_max_dirent (int oi, char c, char const *s) +{ + uintmax_t n; + enum strtol_error e = xstrtoumax (s, NULL, 10, &n, ""); + + if (e == LONGINT_OK) + { + max_fifo_dir_fifos = n; + if (n == max_fifo_dir_fifos) + { + if (0 < max_fifo_dir_fifos) + return; + e = LONGINT_INVALID; + } + else + e = LONGINT_OVERFLOW; + } + + xstrtol_fatal (e, oi, c, long_options, s); +} + + +int +main (int argc, char **argv) +{ + int c = 0, i; + int oi = -1; + char **new_argv; + struct fstatus *fstatus; + struct Tokens tok; + char **files; + char *fifo; + int nfiles = 0; + + files_from_opt_len = FILES_FROM_OPT_LEN; + files_from_opt = xmalloc (files_from_opt_len + 1); + + memcpy (files_from_opt, FILES_FROM_OPT, files_from_opt_len); + + initialize_main (&argc, &argv); + program_name = argv[0]; + setlocale (LC_ALL, ""); + bindtextdomain (PACKAGE, LOCALEDIR); + textdomain (PACKAGE); + + initialize_exit_failure (EXIT_FAILURE); + atexit (exit_cleanup); + + /* This signal handler setup block is basically lifted straight out of + sort.c, which also manages temporary files that need to be cleaned up + before exiting, if possible. */ + { + size_t i; + +#if SA_NOCLDSTOP + struct sigaction act; + + sigemptyset (&caught_signals); + for (i = 0; i < nsigs; i++) + { + sigaction (all_sig[i], NULL, &act); + if (act.sa_handler != SIG_IGN) + sigaddset (&caught_signals, all_sig[i]); + } + + act.sa_handler = sighandler; + act.sa_mask = caught_signals; + act.sa_flags = 0; + + for (i = 0; i < nsigs; i++) + if (sigismember (&caught_signals, all_sig[i])) + sigaction (all_sig[i], &act, NULL); +#else + for (i = 0; i < nsigs; i++) + if (signal (all_sig[i], SIG_IGN) != SIG_IGN) + { + signal (all_sig[i], sighandler); + siginterrupt (all_sig[i], 1); + } +#endif + } + + + parse_long_options (argc, argv, PROGRAM_NAME, PACKAGE_NAME, VERSION, + usage, AUTHORS, (char const *) NULL); + + while ((c = getopt_long (argc, argv, "+T:M:", long_options, &oi)) != -1) + switch (c) + { + case 'T': + set_temp_dir (optarg); + break; + + case 'M': + specify_max_dirent (oi, c, optarg); + break; + + default: + usage (EXIT_FAILURE); + break; + } + + if (argc - optind < 2) + usage (EXIT_FAILURE); + + if (!fifo_dir_base) + { + char const *tmp_dir = getenv ("TMPDIR"); + set_temp_dir (tmp_dir ? tmp_dir : DEFAULT_TMPDIR); + } + + argc -= optind; + argv += optind; + + new_argv = xnmalloc (argc + 1, sizeof *argv); + memcpy (new_argv, argv, (argc + 1) * sizeof *argv); + + for (i = 1; i < argc; i++) + { + char *p = new_argv[i]; + + new_argv[i] = handle_arg (i, new_argv[i]); + } + + if (files_from) + { + FILE *stream; + + if (STREQ (files_from, "-")) + stream = stdin; + else + { + stream = fopen (files_from, "r"); + if (stream == NULL) + error (EXIT_FAILURE, errno, _("cannot open %s for reading"), + quote (files_from)); + } + + readtokens0_init (&tok); + + if (! readtokens0 (stream, &tok)) + error (EXIT_FAILURE, 0, _("cannot read file names from %s"), + quote (files_from)); + + files = tok.tok; + nfiles = tok.n_tok; + } + + if (files_from) + { + pid_t writer_pid; + + for (i = 0; i < nfiles; i++) + if ((fifo = handle_arg (i, files[i])) != NULL) + files[i] = fifo; + + fifo = new_fifo (files_from); + + writer_pid = safe_fork (); + + if (0 < writer_pid) + { + size_t fifo_len = strlen (fifo) + 1; + files_from_opt = xrealloc (files_from_opt, + files_from_opt_len + fifo_len); + + /* Put the new --files0-from=F option in place of the old one. */ + memcpy (files_from_opt + files_from_opt_len, fifo, fifo_len); + new_argv[files_from_opt_i] = files_from_opt; + } + else if (writer_pid == 0) + { + int writefd; + size_t written; + size_t arglen; + /* This will block. */ + if ((writefd = open (fifo, O_WRONLY)) == -1) + error (EXIT_FAILURE, errno, _("open failed: %s"), fifo); + for (i = 0; i < nfiles; i++) + { + arglen = strlen (files[i]) + 1; + if ((written = write (writefd, files[i], arglen)) != arglen) + error (EXIT_FAILURE, errno, _("write failed: %s"), fifo); + } + return 0; + } + else + error (EXIT_FAILURE, errno, _("fork failed")); + } + + + command_pid = safe_fork (); + + if (command_pid > 0) + { + struct sigaction alarm_handler; + + if (--nprocs) + { + sigemptyset (&alarm_handler.sa_mask); + + alarm_handler.sa_handler = reap_some; + + /* Restart the blocking wait() after we clean up zombie helpers. */ + alarm_handler.sa_flags = SA_RESTART; + + sigaction (SIGALRM, &alarm_handler, NULL); + + /* This sets an alarm for itself. It will run every + SECONDS_BETWEEN_REAPS seconds while we're waiting for COMMAND + to finish. */ + reap_some (0); + } + + /* This blocks. We're stuck here (except for occasional non-blocking + reap sessions for helper children) until COMMAND is done. */ + reap (command_pid); + + if (WIFEXITED (command_status)) + command_status = WEXITSTATUS (command_status); + else if (WIFSIGNALED (command_status)) + command_status = WTERMSIG (command_status) + 128; + + return command_status; + } + else if (command_pid == 0) + { + + execvp (new_argv[0], new_argv); + + error (EXIT_FAILURE, errno, _("exec of %s failed"), + quote (new_argv[0])); + } + else + error (EXIT_FAILURE, 0, _("fork failed")); +} + +/* + * Local variables: + * indent-tabs-mode: nil + * End: + */ diff --git a/tests/misc/Makefile.am b/tests/misc/Makefile.am index 17a0ec0..3a98138 100644 --- a/tests/misc/Makefile.am +++ b/tests/misc/Makefile.am @@ -116,6 +116,7 @@ TESTS = \ test-diag \ tsort \ tty-eof \ - unexpand + unexpand \ + zargs include $(top_srcdir)/tests/check.mk diff --git a/tests/misc/help-version b/tests/misc/help-version index 3696736..0fde782 100755 --- a/tests/misc/help-version +++ b/tests/misc/help-version @@ -143,6 +143,7 @@ groups_args=--version pathchk_args=$tmp_in yes_args=--version logname_args=--version +zargs_args=--version nohup_args=--version printf_args=foo seq_args=10 diff --git a/tests/misc/zargs b/tests/misc/zargs new file mode 100755 index 0000000..ae3894e --- /dev/null +++ b/tests/misc/zargs @@ -0,0 +1,82 @@ +#!/bin/sh +# Test zargs + +# Copyright (C) 2008 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 3 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, see . + +: ${srcdir=.} +. $srcdir/../require-perl + +me=`echo $0|sed 's,.*/,,'` +exec $PERL -w -I$srcdir/.. -MCoreutils -M"CuTmpdir qw($me)" -- - <<\EOF +#/ +require 5.003; +use strict; + +(my $program_name = $0) =~ s|.*/||; + +my $prog = 'zargs'; + +# get rid of potential differences between invocation and environments +my $subst = {OUT_SUBST => 's/(?:\S+zargs.{6}|\s+)//g'}; + +# Turn off localization of executable's ouput. address@hidden(LANGUAGE LANG LC_ALL)} = ('C') x 3; + +open my $ofh, ">", "bzip2" or die "Couldn't open bzip2 for write: $!"; +print $ofh "#!/bin/sh\ntail -n +2\n"; +close $ofh; +system ("chmod +x bzip2"); + +$ENV{PATH} = ".:$ENV{PATH}"; + +my @Tests = + ( + # invalid naked invocation + ['usage', {EXIT=>1}, + {ERR => "Try `$prog --help' for more information.\n"}], + + # invalid invocation with no command operands + ['usage-2', 'wc', {EXIT=>1}, + {ERR => "Try `$prog --help' for more information.\n"}], + + # successful invocation with unmodified input + ['ok', 'wc', {IN=>{in=>'a'}}, {OUT => "0 1 1 in\n"}], + + # successful invocation with option + ['ok-1', 'wc', '-l', {IN=>{in=>'a'}}, {OUT => "0 in\n"}], + + # successful invocation with `compressed' input + ['ok-2', 'wc', {IN=>{in=>"BZ\na"}}, $subst, {OUT => "011/in"}], + + # specification of an invalid temporary directory + ['bad', '-T', 'does/not/exist', 'wc', {IN=>{in=>"BZ\na"}}, '"ls bzip2"', + {EXIT=>1}, + {ERR_SUBST => 's/\/zargs.+/\/zargs/'}, + {ERR => "$prog: cannot create temporary directory: ". + "does/not/exist/zargs\n"}], + + # need at least -M of 1 + ['bad-2', '-M', '0', 'wc', {IN=>{in=>"BZ\na"}}, '"ls bzip2"', + {EXIT=>1}, {ERR => "$prog: invalid -M argument `0'\n"}], + + ); + +my $save_temps = $ENV{DEBUG}; +my $verbose = $ENV{VERBOSE}; + +my $fail = run_tests ($program_name, $prog, address@hidden, $save_temps, $verbose); +exit $fail; +EOF -- 1.5.2.5