[tar] tar: allow passing args to commands called from tar

Pavel Raiskup praiskup at fedoraproject.org
Tue Mar 19 13:00:26 UTC 2013


commit 5e2150f9ed4f12a898fe08d87454f7d78d1be484
Author: Pavel Raiskup <praiskup at redhat.com>
Date:   Tue Mar 19 13:52:35 2013 +0100

    tar: allow passing args to commands called from tar
    
    This allows us to run ./configure --with-lzma="xz --format=lzma" - thus the
    default --lzma compressor is xz (no need to have dependency on
    xz-lzma-compat package).
    
    Resolves: #819187
    Version: 2:1.26-20

 tar-1.26-command-args.patch | 2279 +++++++++++++++++++++++++++++++++++++++++++
 tar.spec                    |   11 +-
 2 files changed, 2289 insertions(+), 1 deletions(-)
---
diff --git a/tar-1.26-command-args.patch b/tar-1.26-command-args.patch
new file mode 100644
index 0000000..c99324e
--- /dev/null
+++ b/tar-1.26-command-args.patch
@@ -0,0 +1,2279 @@
+diff --git a/NEWS b/NEWS
+index 8f3c416..421e5fa 100644
+--- a/NEWS
++++ b/NEWS
+@@ -1,6 +1,24 @@
+ GNU tar NEWS - User visible changes. 2012-11-19
+ Please send GNU tar bug reports to <bug-tar at gnu.org>
+ 
++* Passing command line arguments to external commands.
++
++Any option taking a command name as its argument now accepts a full
++command line as well.  Thus, it is now possible to pass additional
++arguments to invoked programs.  The affected options are:
++
++  --checkpoint-action=exec
++  -I, --use-compress-program
++  -F, --info-script
++  --to-command
++
++Furthermore, if any additional information is supplied to such a
++command via environment variables, these variables can now be used in
++the command line itself.  Care should be taken to escape them, to
++prevent from being expanded too early, for example:
++
++  tar -x -f a.tar --info-script='changevol $TAR_ARCHIVE $TAR_VOLUME'
++
+ ** Sparse files with large data
+ 
+ When creating a PAX-format archive, tar no longer arbitrarily restricts
+diff --git a/doc/tar.texi b/doc/tar.texi
+index a0e3d5f..d678db9 100644
+--- a/doc/tar.texi
++++ b/doc/tar.texi
+@@ -1801,13 +1801,14 @@ and @option{--interactive} options (@pxref{interactive}).
+ * Synopsis::
+ * using tar options::
+ * Styles::
+-* All Options::
+-* help::
+-* defaults::
+-* verbose::
+-* checkpoints::
+-* warnings::
+-* interactive::
++* All Options::           All @command{tar} Options.
++* help::                  Where to Get Help.
++* defaults::              What are the Default Values.
++* verbose::               Checking @command{tar} progress.
++* checkpoints::           Checkpoints.
++* warnings::              Controlling Warning Messages.
++* interactive::           Asking for Confirmation During Operations.
++* external::              Running External Commands.
+ @end menu
+ 
+ @node Synopsis
+@@ -2788,14 +2789,14 @@ Send verbose output to @var{file} instead of to standard output.
+ 
+ @opsummary{info-script}
+ @opsummary{new-volume-script}
+- at item --info-script=@var{script-file}
+- at itemx --new-volume-script=@var{script-file}
+- at itemx -F @var{script-file}
++ at item --info-script=@var{command}
++ at itemx --new-volume-script=@var{command}
++ at itemx -F @var{command}
+ 
+-When @command{tar} is performing multi-tape backups, @var{script-file} is run
+-at the end of each tape.  If @var{script-file} exits with nonzero status,
+- at command{tar} fails immediately.  @xref{info-script}, for a detailed
+-discussion of @var{script-file}.
++When @command{tar} is performing multi-tape backups, @var{command} is run
++at the end of each tape.  If it exits with nonzero status,
++ at command{tar} fails immediately.  @xref{info-script}, for a detailed 
++discussion of this feature.
+ 
+ @opsummary{interactive}
+ @item --interactive
+@@ -4009,17 +4010,22 @@ checkpoint:
+ $ @kbd{tar -c --checkpoint=1000 --checkpoint-action=sleep=30}
+ @end smallexample
+ 
++ at anchor{checkpoint exec}
+ @cindex @code{exec}, checkpoint action
+-Finally, the @code{exec} action executes a given external program.
++Finally, the @code{exec} action executes a given external command.
+ For example:
+ 
+ @smallexample
+ $ @kbd{tar -c --checkpoint=1000 --checkpoint-action=exec=/sbin/cpoint}
+ @end smallexample
+ 
+-This program is executed using @command{/bin/sh -c}, with no
+-additional arguments.  Its exit code is ignored.  It gets a copy of
+- at command{tar}'s environment plus the following variables:
++The supplied command can be any valid command invocation, with or
++without additional command line arguments.  If it does contain
++arguments, don't forget to quote it to prevent it from being split by
++the shell.  @xref{external, Running External Commands}, for more detail.
++
++The command gets a copy of @command{tar}'s environment plus the
++following variables:
+ 
+ @table @env
+ @vrindex TAR_VERSION, checkpoint script environment
+@@ -4049,6 +4055,18 @@ Format of the archive being processed. @xref{Formats}, for a complete
+ list of archive format names.
+ @end table
+ 
++These environment variables can also be passed as arguments to the
++command, provided that they are properly escaped, for example:
++
++ at smallexample
++ at kbd{tar -c -f arc.tar \
++     --checkpoint-action='exec=/sbin/cpoint $TAR_FILENAME'}
++ at end smallexample
++
++ at noindent
++Notice single quotes to prevent variable names from being expanded by
++the shell when invoking @command{tar}.
++
+ Any number of actions can be defined, by supplying several
+ @option{--checkpoint-action} options in the command line.  For
+ example, the command below displays two messages, pauses
+@@ -4263,6 +4281,42 @@ named pipe to receive the archive, and having the consumer process to
+ read from that named pipe.  This has the advantage of letting standard
+ output free to receive verbose output, all separate from errors.
+ 
++ at node external
++ at section Running External Commands
++
++Certain @GNUTAR{} operations imply running external commands that you
++supply on the command line.  One of such operations is checkpointing,
++described above (@pxref{checkpoint exec}).  Another example of this
++feature is the @option{-I} option, which allows you to supply the
++program to use for compressing or decompressing the archive
++(@pxref{use-compress-program}).
++
++Whenever such operation is requested, @command{tar} first splits the
++supplied command into words much like the shell does.  It then treats
++the first word as the name of the program or the shell script to execute
++and the rest of words as its command line arguments.  The program,
++unless given as an absolute file name, is searched in the shell's
++ at env{PATH}.
++
++Any additional information is normally supplied to external commands
++in environment variables, specific to each particular operation.  For
++example, the @option{--checkpoint-action=exec} option, defines the
++ at env{TAR_ARCHIVE} variable to the name of the archive being worked
++upon.  You can, should the need be, use these variables in the 
++command line of the external command.  For example:
++
++ at smallexample
++$ @kbd{tar -x -f archive.tar \
++    --checkpoint=exec='printf "%04d in %32s\r" $TAR_CHECKPOINT $TAR_ARCHIVE'}
++ at end smallexample
++
++ at noindent
++This command prints for each checkpoint its number and the name of the
++archive, using the same output line on the screen.
++
++Notice the use of single quotes to prevent variable names from being
++expanded by the shell when invoking @command{tar}.
++
+ @node operations
+ @chapter @GNUTAR{} Operations
+ 
+@@ -5460,11 +5514,13 @@ file to the standard input of an external program:
+ @opindex to-command
+ @item --to-command=@var{command}
+ Extract files and pipe their contents to the standard input of
+- at var{command}. When this option is used, instead of creating the
++ at var{command}.  When this option is used, instead of creating the
+ files specified, @command{tar} invokes @var{command} and pipes the
+-contents of the files to its standard output. The @var{command} may
+-contain command line arguments. The program is executed via
+- at code{sh -c}. Notice, that @var{command} is executed once for each regular file
++contents of the files to its standard output.  The @var{command} may
++contain command line arguments (see @ref{external, Running External Commands},
++for more detail).
++
++Notice, that @var{command} is executed once for each regular file
+ extracted. Non-regular files (directories, etc.) are ignored when this
+ option is used.
+ @end table
+@@ -5562,6 +5618,20 @@ Format of the archive being processed. @xref{Formats}, for a complete
+ list of archive format names.
+ @end table
+ 
++These variables are defined prior to executing the command, so you can
++pass them as arguments, if you prefer.  For example, if the command
++ at var{proc} takes the member name and size as its arguments, then you
++could do: 
++
++ at smallexample
++$ @kbd{tar -x -f archive.tar \
++       --to-command='proc $TAR_FILENAME $TAR_SIZE'}
++ at end smallexample
++
++ at noindent
++Notice single quotes to prevent variable names from being expanded by
++the shell when invoking @command{tar}.
++
+ If @var{command} exits with a non-0 status, @command{tar} will print
+ an error message similar to the following:
+ 
+@@ -8972,21 +9042,29 @@ suffix.  The following suffixes are recognized:
+ @item @samp{.xz} @tab @command{xz}
+ @end multitable
+ 
++ at anchor{use-compress-program}
+ @opindex use-compress-program
+- at item --use-compress-program=@var{prog}
+- at itemx -I=@var{prog}
+-Use external compression program @var{prog}.  Use this option if you
++ at item --use-compress-program=@var{command}
++ at itemx -I=@var{command}
++Use external compression program @var{command}.  Use this option if you
+ are not happy with the compression program associated with the suffix
+ at compile time or if you have a compression program that @GNUTAR{}
+-does not support.  There are two requirements to which @var{prog}
+-should comply:
++does not support.  The @var{command} argument is a valid command
++invocation, as you would type it at the command line prompt, with any
++additional options as needed.  Enclose it in quotes if it contains
++white space (see @ref{external, Running External Commands}, for more detail).
++
++The @var{command} should follow two conventions:
+ 
+-First, when called without options, it should read data from standard
+-input, compress it and output it on standard output.
++First, when invoked without additional options, it should read data
++from standard input, compress it and output it on standard output.
+ 
+-Secondly, if called with @option{-d} argument, it should do exactly
+-the opposite, i.e., read the compressed data from the standard input
+-and produce uncompressed data on the standard output.
++Secondly, if invoked with the additional @option{-d} option, it should
++do exactly the opposite, i.e., read the compressed data from the
++standard input and produce uncompressed data on the standard output.
++
++The latter requirement means that you must not use the @option{-d}
++option as a part of the @var{command} itself.
+ @end table
+ 
+ @cindex gpg, using with tar
+@@ -10458,10 +10536,10 @@ maximum tape length, you might avoid the problem entirely.
+ 
+ @xopindex{info-script, short description}
+ @xopindex{new-volume-script, short description}
+- at item -F @var{file}
+- at itemx --info-script=@var{file}
+- at itemx --new-volume-script=@var{file}
+-Execute @file{file} at end of each tape.  This implies
++ at item -F @var{command}
++ at itemx --info-script=@var{command}
++ at itemx --new-volume-script=@var{command}
++Execute @var{command} at end of each tape.  This implies
+ @option{--multi-volume} (@option{-M}).  @xref{info-script}, for a detailed
+ description of this option.
+ @end table
+@@ -11342,19 +11420,20 @@ volume, and instruct @command{tar} to use it instead of its normal
+ prompting procedure:
+ 
+ @table @option
+- at item --info-script=@var{script-name}
+- at itemx --new-volume-script=@var{script-name}
+- at itemx -F @var{script-name}
+-Specify the full name of the volume script to use.  The script can be
+-used to eject cassettes, or to broadcast messages such as
++ at item --info-script=@var{command}
++ at itemx --new-volume-script=@var{command}
++ at itemx -F @var{command}
++Specify the command to invoke when switching volumes.  The @var{command}
++can be used to eject cassettes, or to broadcast messages such as
+ @samp{Someone please come change my tape} when performing unattended
+ backups.
+ @end table
+ 
+-The @var{script-name} is executed without any command line
+-arguments.  It inherits @command{tar}'s shell environment.
+-Additional data is passed to it via the following
+-environment variables:
++The @var{command} can contain additional options, if such are needed.
++ at xref{external, Running External Commands}, for a detailed discussion
++of the way @GNUTAR{} runs external commands.  It inherits
++ at command{tar}'s shell environment.  Additional data is passed to it
++via the following environment variables:
+ 
+ @table @env
+ @vrindex TAR_VERSION, info script environment variable
+@@ -11389,6 +11468,10 @@ File descriptor which can be used to communicate the new volume
+ name to @command{tar}.
+ @end table
+ 
++These variables can be used in the @var{command} itself, provided that
++they are properly quoted to prevent them from being expanded by the
++shell that invokes @command{tar}.
++
+ The volume script can instruct @command{tar} to use new archive name,
+ by writing in to file descriptor @env{$TAR_FD} (see below for an example).
+ 
+diff --git a/lib/Makefile.am b/lib/Makefile.am
+index d73fac8..24e12e0 100644
+--- a/lib/Makefile.am
++++ b/lib/Makefile.am
+@@ -28,7 +28,15 @@ BUILT_SOURCES = rmt-command.h
+ CLEANFILES = rmt-command.h rmt-command.h-t
+ INCLUDES = -I$(top_srcdir)/gnu -I../ -I../gnu
+ 
+-noinst_HEADERS = system.h system-ioctl.h rmt.h paxlib.h stdopen.h xattr-at.h
++noinst_HEADERS = \
++ paxlib.h\
++ rmt.h\
++ stdopen.h\
++ system.h\
++ system-ioctl.h\
++ wordsplit.h\
++ xattr-at.h
++
+ libtar_a_SOURCES = \
+   paxerror.c paxexit-status.c paxlib.h paxnames.c \
+   prepargs.c prepargs.h \
+@@ -36,6 +44,7 @@ libtar_a_SOURCES = \
+   rmt.h \
+   stdopen.c stdopen.h \
+   system.h system-ioctl.h \
++  wordsplit.c\
+   xattr-at.c
+ 
+ if !TAR_COND_XATTR_H
+diff --git a/lib/wordsplit.c b/lib/wordsplit.c
+new file mode 100644
+index 0000000..bd5d59d
+--- /dev/null
++++ b/lib/wordsplit.c
+@@ -0,0 +1,1625 @@
++/* wordsplit - a word splitter
++   Copyright (C) 2009-2013 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 <http://www.gnu.org/licenses/>.
++
++   Written by Sergey Poznyakoff
++*/
++
++#ifdef HAVE_CONFIG_H
++# include <config.h>
++#endif
++
++#include <errno.h>
++#include <ctype.h>
++#include <unistd.h>
++#include <stdlib.h>
++#include <string.h>
++#include <stdio.h>
++#include <stdarg.h>
++
++#if ENABLE_NLS
++# include <gettext.h>
++#else
++# define gettext(msgid) msgid
++#endif
++#define _(msgid) gettext (msgid)
++#define N_(msgid) msgid
++
++#include <wordsplit.h>
++
++#define ISWS(c) ((c)==' '||(c)=='\t'||(c)=='\n')
++#define ISDELIM(ws,c) \
++  (strchr ((ws)->ws_delim, (c)) != NULL)
++#define ISPUNCT(c) (strchr("!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~",(c))!=NULL)
++#define ISUPPER(c) ('A' <= ((unsigned) (c)) && ((unsigned) (c)) <= 'Z')
++#define ISLOWER(c) ('a' <= ((unsigned) (c)) && ((unsigned) (c)) <= 'z')
++#define ISALPHA(c) (ISUPPER(c) || ISLOWER(c))
++#define ISDIGIT(c) ('0' <= ((unsigned) (c)) && ((unsigned) (c)) <= '9')
++#define ISXDIGIT(c) (strchr("abcdefABCDEF", c)!=NULL)
++#define ISALNUM(c) (ISALPHA(c) || ISDIGIT(c))
++#define ISPRINT(c) (' ' <= ((unsigned) (c)) && ((unsigned) (c)) <= 127)
++
++#define ALLOC_INIT 128
++#define ALLOC_INCR 128
++
++static void
++_wsplt_alloc_die (struct wordsplit *wsp)
++{
++  wsp->ws_error (_("memory exhausted"));
++  abort ();
++}
++
++static void
++_wsplt_error (const char *fmt, ...)
++{
++  va_list ap;
++
++  va_start (ap, fmt);
++  vfprintf (stderr, fmt, ap);
++  va_end (ap);
++  fputc ('\n', stderr);
++}
++
++static void wordsplit_free_nodes (struct wordsplit *);
++
++static int
++_wsplt_nomem (struct wordsplit *wsp)
++{
++  errno = ENOMEM;
++  wsp->ws_errno = WRDSE_NOSPACE;
++  if (wsp->ws_flags & WRDSF_ENOMEMABRT)
++    wsp->ws_alloc_die (wsp);
++  if (wsp->ws_flags & WRDSF_SHOWERR)
++    wordsplit_perror (wsp);
++  if (!(wsp->ws_flags & WRDSF_REUSE))
++    wordsplit_free (wsp);
++  wordsplit_free_nodes (wsp);
++  return wsp->ws_errno;
++}
++
++static void
++wordsplit_init0 (struct wordsplit *wsp)
++{
++  if (wsp->ws_flags & WRDSF_REUSE)
++    {
++      if (!(wsp->ws_flags & WRDSF_APPEND))
++	wordsplit_free_words (wsp);
++    }
++  else
++    {
++      wsp->ws_wordv = NULL;
++      wsp->ws_wordc = 0;
++      wsp->ws_wordn = 0;
++    }
++
++  wsp->ws_errno = 0;
++  wsp->ws_head = wsp->ws_tail = NULL;
++}
++
++static int
++wordsplit_init (struct wordsplit *wsp, const char *input, size_t len,
++		int flags)
++{
++  wsp->ws_flags = flags;
++
++  if (!(wsp->ws_flags & WRDSF_ALLOC_DIE))
++    wsp->ws_alloc_die = _wsplt_alloc_die;
++  if (!(wsp->ws_flags & WRDSF_ERROR))
++    wsp->ws_error = _wsplt_error;
++
++  if (!(wsp->ws_flags & WRDSF_NOVAR)
++      && !(wsp->ws_flags & (WRDSF_ENV | WRDSF_GETVAR)))
++    {
++      errno = EINVAL;
++      wsp->ws_errno = WRDSE_USAGE;
++      if (wsp->ws_flags & WRDSF_SHOWERR)
++	wordsplit_perror (wsp);
++      return wsp->ws_errno;
++    }
++
++  if (!(wsp->ws_flags & WRDSF_NOCMD))
++    {
++      errno = EINVAL;
++      wsp->ws_errno = WRDSE_NOSUPP;
++      if (wsp->ws_flags & WRDSF_SHOWERR)
++	wordsplit_perror (wsp);
++      return wsp->ws_errno;
++    }
++
++  if (wsp->ws_flags & WRDSF_SHOWDBG)
++    {
++      if (!(wsp->ws_flags & WRDSF_DEBUG))
++	{
++	  if (wsp->ws_flags & WRDSF_ERROR)
++	    wsp->ws_debug = wsp->ws_error;
++	  else if (wsp->ws_flags & WRDSF_SHOWERR)
++	    wsp->ws_debug = _wsplt_error;
++	  else
++	    wsp->ws_flags &= ~WRDSF_SHOWDBG;
++	}
++    }
++
++  wsp->ws_input = input;
++  wsp->ws_len = len;
++
++  if (!(wsp->ws_flags & WRDSF_DOOFFS))
++    wsp->ws_offs = 0;
++
++  if (!(wsp->ws_flags & WRDSF_DELIM))
++    wsp->ws_delim = " \t\n";
++
++  if (!(wsp->ws_flags & WRDSF_COMMENT))
++    wsp->ws_comment = NULL;
++
++  if (!(wsp->ws_flags & WRDSF_CLOSURE))
++    wsp->ws_closure = NULL;
++
++  wsp->ws_endp = 0;
++
++  wordsplit_init0 (wsp);
++
++  return 0;
++}
++
++static int
++alloc_space (struct wordsplit *wsp, size_t count)
++{
++  size_t offs = (wsp->ws_flags & WRDSF_DOOFFS) ? wsp->ws_offs : 0;
++  char **ptr;
++  size_t newalloc;
++
++  if (wsp->ws_wordv == NULL)
++    {
++      newalloc = offs + count > ALLOC_INIT ? count : ALLOC_INIT;
++      ptr = calloc (newalloc, sizeof (ptr[0]));
++    }
++  else if (wsp->ws_wordn < offs + wsp->ws_wordc + count)
++    {
++      newalloc = offs + wsp->ws_wordc +
++	(count > ALLOC_INCR ? count : ALLOC_INCR);
++      ptr = realloc (wsp->ws_wordv, newalloc * sizeof (ptr[0]));
++    }
++  else
++    return 0;
++
++  if (ptr)
++    {
++      wsp->ws_wordn = newalloc;
++      wsp->ws_wordv = ptr;
++    }
++  else
++    return _wsplt_nomem (wsp);
++  return 0;
++}
++
++
++/* Node state flags */
++#define _WSNF_NULL     0x01	/* null node (a noop) */
++#define _WSNF_WORD     0x02	/* node contains word in v.word */
++#define _WSNF_QUOTE    0x04	/* text is quoted */
++#define _WSNF_NOEXPAND 0x08	/* text is not subject to expansion */
++#define _WSNF_JOIN     0x10	/* node must be joined with the next node */
++#define _WSNF_SEXP     0x20	/* is a sed expression */
++
++#define _WSNF_EMPTYOK  0x0100	/* special flag indicating that
++				   wordsplit_add_segm must add the
++				   segment even if it is empty */
++
++struct wordsplit_node
++{
++  struct wordsplit_node *prev;	/* Previous element */
++  struct wordsplit_node *next;	/* Next element */
++  int flags;			/* Node flags */
++  union
++  {
++    struct
++    {
++      size_t beg;		/* Start of word in ws_input */
++      size_t end;		/* End of word in ws_input */
++    } segm;
++    char *word;
++  } v;
++};
++
++static const char *
++wsnode_flagstr (int flags)
++{
++  static char retbuf[6];
++  char *p = retbuf;
++
++  if (flags & _WSNF_WORD)
++    *p++ = 'w';
++  else if (flags & _WSNF_NULL)
++    *p++ = 'n';
++  else
++    *p++ = '-';
++  if (flags & _WSNF_QUOTE)
++    *p++ = 'q';
++  else
++    *p++ = '-';
++  if (flags & _WSNF_NOEXPAND)
++    *p++ = 'E';
++  else
++    *p++ = '-';
++  if (flags & _WSNF_JOIN)
++    *p++ = 'j';
++  else
++    *p++ = '-';
++  if (flags & _WSNF_SEXP)
++    *p++ = 's';
++  else
++    *p++ = '-';
++  *p = 0;
++  return retbuf;
++}
++
++static const char *
++wsnode_ptr (struct wordsplit *wsp, struct wordsplit_node *p)
++{
++  if (p->flags & _WSNF_NULL)
++    return "";
++  else if (p->flags & _WSNF_WORD)
++    return p->v.word;
++  else
++    return wsp->ws_input + p->v.segm.beg;
++}
++
++static size_t
++wsnode_len (struct wordsplit_node *p)
++{
++  if (p->flags & _WSNF_NULL)
++    return 0;
++  else if (p->flags & _WSNF_WORD)
++    return strlen (p->v.word);
++  else
++    return p->v.segm.end - p->v.segm.beg;
++}
++
++static int
++wsnode_new (struct wordsplit *wsp, struct wordsplit_node **pnode)
++{
++  struct wordsplit_node *node = calloc (1, sizeof (*node));
++  if (!node)
++    return _wsplt_nomem (wsp);
++  *pnode = node;
++  return 0;
++}
++
++static void
++wsnode_free (struct wordsplit_node *p)
++{
++  if (p->flags & _WSNF_WORD)
++    free (p->v.word);
++  free (p);
++}
++
++static void
++wsnode_append (struct wordsplit *wsp, struct wordsplit_node *node)
++{
++  node->next = NULL;
++  node->prev = wsp->ws_tail;
++  if (wsp->ws_tail)
++    wsp->ws_tail->next = node;
++  else
++    wsp->ws_head = node;
++  wsp->ws_tail = node;
++}
++
++static void
++wsnode_remove (struct wordsplit *wsp, struct wordsplit_node *node)
++{
++  struct wordsplit_node *p;
++
++  p = node->prev;
++  if (p)
++    {
++      p->next = node->next;
++      if (!node->next)
++	p->flags &= ~_WSNF_JOIN;
++    }
++  else
++    wsp->ws_head = node->next;
++
++  p = node->next;
++  if (p)
++    p->prev = node->prev;
++  else
++    wsp->ws_tail = node->prev;
++
++  node->next = node->prev = NULL;
++}
++
++static void
++wsnode_insert (struct wordsplit *wsp, struct wordsplit_node *node,
++	       struct wordsplit_node *anchor, int before)
++{
++  if (!wsp->ws_head)
++    {
++      node->next = node->prev = NULL;
++      wsp->ws_head = wsp->ws_tail = node;
++    }
++  else if (before)
++    {
++      if (anchor->prev)
++	wsnode_insert (wsp, node, anchor->prev, 0);
++      else
++	{
++	  node->prev = NULL;
++	  node->next = anchor;
++	  anchor->prev = node;
++	  wsp->ws_head = node;
++	}
++    }
++  else
++    {
++      struct wordsplit_node *p;
++
++      p = anchor->next;
++      if (p)
++	p->prev = node;
++      else
++	wsp->ws_tail = node;
++      node->next = p;
++      node->prev = anchor;
++      anchor->next = node;
++    }
++}
++
++static int
++wordsplit_add_segm (struct wordsplit *wsp, size_t beg, size_t end, int flg)
++{
++  struct wordsplit_node *node;
++  int rc;
++
++  if (end == beg && !(flg & _WSNF_EMPTYOK))
++    return 0;
++  rc = wsnode_new (wsp, &node);
++  if (rc)
++    return rc;
++  node->flags = flg & ~(_WSNF_WORD | _WSNF_EMPTYOK);
++  node->v.segm.beg = beg;
++  node->v.segm.end = end;
++  wsnode_append (wsp, node);
++  return 0;
++}
++
++static void
++wordsplit_free_nodes (struct wordsplit *wsp)
++{
++  struct wordsplit_node *p;
++
++  for (p = wsp->ws_head; p;)
++    {
++      struct wordsplit_node *next = p->next;
++      wsnode_free (p);
++      p = next;
++    }
++  wsp->ws_head = wsp->ws_tail = NULL;
++}
++
++static void
++wordsplit_dump_nodes (struct wordsplit *wsp)
++{
++  struct wordsplit_node *p;
++  int n = 0;
++
++  for (p = wsp->ws_head, n = 0; p; p = p->next, n++)
++    {
++      if (p->flags & _WSNF_WORD)
++	wsp->ws_debug ("%4d: %p: %#04x (%s):%s;",
++		       n, p, p->flags, wsnode_flagstr (p->flags), p->v.word);
++      else
++	wsp->ws_debug ("%4d: %p: %#04x (%s):%.*s;",
++		       n, p, p->flags, wsnode_flagstr (p->flags),
++		       (int) (p->v.segm.end - p->v.segm.beg),
++		       wsp->ws_input + p->v.segm.beg);
++    }
++}
++
++static int
++coalesce_segment (struct wordsplit *wsp, struct wordsplit_node *node)
++{
++  struct wordsplit_node *p, *end;
++  size_t len = 0;
++  char *buf, *cur;
++  int stop;
++
++  for (p = node; p && (p->flags & _WSNF_JOIN); p = p->next)
++    {
++      len += wsnode_len (p);
++    }
++  len += wsnode_len (p);
++  end = p;
++
++  buf = malloc (len + 1);
++  if (!buf)
++    return _wsplt_nomem (wsp);
++  cur = buf;
++
++  p = node;
++  for (stop = 0; !stop;)
++    {
++      struct wordsplit_node *next = p->next;
++      const char *str = wsnode_ptr (wsp, p);
++      size_t slen = wsnode_len (p);
++
++      memcpy (cur, str, slen);
++      cur += slen;
++      if (p != node)
++	{
++	  wsnode_remove (wsp, p);
++	  stop = p == end;
++	  wsnode_free (p);
++	}
++      p = next;
++    }
++
++  *cur = 0;
++
++  node->flags &= ~_WSNF_JOIN;
++
++  if (node->flags & _WSNF_WORD)
++    free (node->v.word);
++  else
++    node->flags |= _WSNF_WORD;
++  node->v.word = buf;
++  return 0;
++}
++
++static int
++wsnode_quoteremoval (struct wordsplit *wsp)
++{
++  struct wordsplit_node *p;
++  void (*uqfn) (char *, const char *, size_t) =
++    (wsp->ws_flags & WRDSF_CESCAPES) ?
++    wordsplit_c_unquote_copy : wordsplit_sh_unquote_copy;
++
++  for (p = wsp->ws_head; p; p = p->next)
++    {
++      const char *str = wsnode_ptr (wsp, p);
++      size_t slen = wsnode_len (p);
++      int unquote;
++
++      if (wsp->ws_flags & WRDSF_QUOTE)
++	{
++	  unquote = !(p->flags & _WSNF_NOEXPAND);
++	}
++      else
++	unquote = 0;
++
++      if (unquote)
++	{
++	  if (!(p->flags & _WSNF_WORD))
++	    {
++	      char *newstr = malloc (slen + 1);
++	      if (!newstr)
++		return _wsplt_nomem (wsp);
++	      memcpy (newstr, str, slen);
++	      newstr[slen] = 0;
++	      p->v.word = newstr;
++	      p->flags |= _WSNF_WORD;
++	    }
++
++	  if (wsp->ws_flags & WRDSF_ESCAPE)
++	    wordsplit_general_unquote_copy (p->v.word, str, slen,
++				            wsp->ws_escape);
++	  else
++	    uqfn (p->v.word, str, slen);
++	}
++    }
++  return 0;
++}
++
++static int
++wsnode_coalesce (struct wordsplit *wsp)
++{
++  struct wordsplit_node *p;
++
++  for (p = wsp->ws_head; p; p = p->next)
++    {
++      if (p->flags & _WSNF_JOIN)
++	if (coalesce_segment (wsp, p))
++	  return 1;
++    }
++  return 0;
++}
++
++static int
++wordsplit_finish (struct wordsplit *wsp)
++{
++  struct wordsplit_node *p;
++  size_t n;
++
++  n = 0;
++
++  for (p = wsp->ws_head; p; p = p->next)
++    n++;
++
++  if (alloc_space (wsp, n + 1))
++    return 1;
++
++  for (p = wsp->ws_head; p; p = p->next)
++    {
++      const char *str = wsnode_ptr (wsp, p);
++      size_t slen = wsnode_len (p);
++      char *newstr = malloc (slen + 1);
++
++      /* Assign newstr first, even if it is NULL.  This way
++         wordsplit_free will work even if we return
++         nomem later. */
++      wsp->ws_wordv[wsp->ws_offs + wsp->ws_wordc] = newstr;
++      if (!newstr)
++	return _wsplt_nomem (wsp);
++      memcpy (newstr, str, slen);
++      newstr[slen] = 0;
++
++      wsp->ws_wordc++;
++
++    }
++  wsp->ws_wordv[wsp->ws_offs + wsp->ws_wordc] = NULL;
++  return 0;
++}
++
++
++/* Variable expansion */
++static int
++node_split_prefix (struct wordsplit *wsp,
++		   struct wordsplit_node **ptail,
++		   struct wordsplit_node *node,
++		   size_t beg, size_t len, int flg)
++{
++  struct wordsplit_node *newnode;
++
++  if (len == 0)
++    return 0;
++  if (wsnode_new (wsp, &newnode))
++    return 1;
++  wsnode_insert (wsp, newnode, *ptail, 0);
++  if (node->flags & _WSNF_WORD)
++    {
++      const char *str = wsnode_ptr (wsp, node);
++      char *newstr = malloc (len + 1);
++      if (!newstr)
++	return _wsplt_nomem (wsp);
++      memcpy (newstr, str + beg, len);
++      newstr[len] = 0;
++      newnode->flags = _WSNF_WORD;
++      newnode->v.word = newstr;
++    }
++  else
++    {
++      newnode->v.segm.beg = node->v.segm.beg + beg;
++      newnode->v.segm.end = newnode->v.segm.beg + len;
++    }
++  newnode->flags |= flg;
++  *ptail = newnode;
++  return 0;
++}
++
++static int
++find_closing_cbrace (const char *str, size_t i, size_t len, size_t * poff)
++{
++  enum
++  { st_init, st_squote, st_dquote } state = st_init;
++  size_t level = 1;
++
++  for (; i < len; i++)
++    {
++      switch (state)
++	{
++	case st_init:
++	  switch (str[i])
++	    {
++	    case '{':
++	      level++;
++	      break;
++
++	    case '}':
++	      if (--level == 0)
++		{
++		  *poff = i;
++		  return 0;
++		}
++	      break;
++
++	    case '"':
++	      state = st_dquote;
++	      break;
++
++	    case '\'':
++	      state = st_squote;
++	      break;
++	    }
++	  break;
++
++	case st_squote:
++	  if (str[i] == '\'')
++	    state = st_init;
++	  break;
++
++	case st_dquote:
++	  if (str[i] == '\\')
++	    i++;
++	  else if (str[i] == '"')
++	    state = st_init;
++	  break;
++	}
++    }
++  return 1;
++}
++
++static const char *
++wordsplit_find_env (struct wordsplit *wsp, const char *name, size_t len)
++{
++  size_t i;
++
++  if (!(wsp->ws_flags & WRDSF_ENV))
++    return NULL;
++
++  if (wsp->ws_flags & WRDSF_ENV_KV)
++    {
++      /* A key-value pair environment */
++      for (i = 0; wsp->ws_env[i]; i++)
++	{
++	  size_t elen = strlen (wsp->ws_env[i]);
++	  if (elen == len && memcmp (wsp->ws_env[i], name, elen) == 0)
++	    return wsp->ws_env[i + 1];
++	  /* Skip the value.  Break the loop if it is NULL. */
++	  i++;
++	  if (wsp->ws_env[i] == NULL)
++	    break;
++	}
++    }
++  else
++    {
++      /* Usual (A=B) environment. */
++      for (i = 0; wsp->ws_env[i]; i++)
++	{
++	  size_t j;
++	  const char *var = wsp->ws_env[i];
++
++	  for (j = 0; j < len; j++)
++	    if (name[j] != var[j])
++	      break;
++	  if (j == len && var[j] == '=')
++	    return var + j + 1;
++	}
++    }
++  return NULL;
++}
++
++static int
++expvar (struct wordsplit *wsp, const char *str, size_t len,
++	struct wordsplit_node **ptail, const char **pend, int flg)
++{
++  size_t i = 0;
++  const char *defstr = NULL;
++  const char *value;
++  const char *vptr;
++  struct wordsplit_node *newnode;
++  const char *start = str - 1;
++
++  if (ISALPHA (str[0]) || str[0] == '_')
++    {
++      for (i = 1; i < len; i++)
++	if (!(ISALNUM (str[i]) || str[i] == '_'))
++	  break;
++      *pend = str + i - 1;
++    }
++  else if (str[0] == '{')
++    {
++      str++;
++      len--;
++      for (i = 1; i < len; i++)
++	if (str[i] == '}' || str[i] == ':')
++	  break;
++      if (str[i] == ':')
++	{
++	  size_t j;
++
++	  defstr = str + i + 1;
++	  if (find_closing_cbrace (str, i + 1, len, &j))
++	    {
++	      wsp->ws_errno = WRDSE_CBRACE;
++	      return 1;
++	    }
++	  *pend = str + j;
++	}
++      else if (str[i] == '}')
++	{
++	  defstr = NULL;
++	  *pend = str + i;
++	}
++      else
++	{
++	  wsp->ws_errno = WRDSE_CBRACE;
++	  return 1;
++	}
++    }
++  else
++    {
++      if (wsnode_new (wsp, &newnode))
++	return 1;
++      wsnode_insert (wsp, newnode, *ptail, 0);
++      *ptail = newnode;
++      newnode->flags = _WSNF_WORD | flg;
++      newnode->v.word = malloc (3);
++      if (!newnode->v.word)
++	return _wsplt_nomem (wsp);
++      newnode->v.word[0] = '$';
++      newnode->v.word[1] = str[0];
++      newnode->v.word[2] = 0;
++      *pend = str;
++      return 0;
++    }
++
++  /* Actually expand the variable */
++  /* str - start of the variable name
++     i   - its length
++     defstr - default replacement str */
++
++  vptr = wordsplit_find_env (wsp, str, i);
++  if (vptr)
++    {
++      value = strdup (vptr);
++      if (!value)
++	return _wsplt_nomem (wsp);
++    }
++  else if (wsp->ws_flags & WRDSF_GETVAR)
++    value = wsp->ws_getvar (str, i, wsp->ws_closure);
++  else if (wsp->ws_flags & WRDSF_UNDEF)
++    {
++      wsp->ws_errno = WRDSE_UNDEF;
++      if (wsp->ws_flags & WRDSF_SHOWERR)
++	wordsplit_perror (wsp);
++      return 1;
++    }
++  else
++    {
++      if (wsp->ws_flags & WRDSF_WARNUNDEF)
++	wsp->ws_error (_("warning: undefined variable `%.*s'"), (int) i, str);
++      if (wsp->ws_flags & WRDSF_KEEPUNDEF)
++	value = NULL;
++      else
++	value = "";
++    }
++  /* FIXME: handle defstr */
++  if (value)
++    {
++      if (flg & _WSNF_QUOTE)
++	{
++	  if (wsnode_new (wsp, &newnode))
++	    return 1;
++	  wsnode_insert (wsp, newnode, *ptail, 0);
++	  *ptail = newnode;
++	  newnode->flags = _WSNF_WORD | _WSNF_NOEXPAND | flg;
++	  newnode->v.word = strdup (value);
++	  if (!newnode->v.word)
++	    return _wsplt_nomem (wsp);
++	}
++      else if (*value == 0)
++	{
++	  /* Empty string is a special case */
++	  if (wsnode_new (wsp, &newnode))
++	    return 1;
++	  wsnode_insert (wsp, newnode, *ptail, 0);
++	  *ptail = newnode;
++	  newnode->flags = _WSNF_NULL;
++	}
++      else
++	{
++	  struct wordsplit ws;
++	  int i;
++
++	  ws.ws_delim = wsp->ws_delim;
++	  if (wordsplit (value, &ws,
++			 WRDSF_NOVAR | WRDSF_NOCMD | WRDSF_DELIM | WRDSF_WS))
++	    {
++	      wordsplit_free (&ws);
++	      return 1;
++	    }
++	  for (i = 0; i < ws.ws_wordc; i++)
++	    {
++	      if (wsnode_new (wsp, &newnode))
++		return 1;
++	      wsnode_insert (wsp, newnode, *ptail, 0);
++	      *ptail = newnode;
++	      newnode->flags = _WSNF_WORD |
++		_WSNF_NOEXPAND |
++		(i + 1 < ws.ws_wordc ? (flg & ~_WSNF_JOIN) : flg);
++	      newnode->v.word = strdup (ws.ws_wordv[i]);
++	      if (!newnode->v.word)
++		return _wsplt_nomem (wsp);
++	    }
++	  wordsplit_free (&ws);
++	}
++    }
++  else if (wsp->ws_flags & WRDSF_KEEPUNDEF)
++    {
++      size_t size = *pend - start + 1;
++
++      if (wsnode_new (wsp, &newnode))
++	return 1;
++      wsnode_insert (wsp, newnode, *ptail, 0);
++      *ptail = newnode;
++      newnode->flags = _WSNF_WORD | _WSNF_NOEXPAND | flg;
++      newnode->v.word = malloc (size + 1);
++      if (!newnode->v.word)
++	return _wsplt_nomem (wsp);
++      memcpy (newnode->v.word, start, size);
++      newnode->v.word[size] = 0;
++    }
++  else
++    {
++      if (wsnode_new (wsp, &newnode))
++	return 1;
++      wsnode_insert (wsp, newnode, *ptail, 0);
++      *ptail = newnode;
++      newnode->flags = _WSNF_NULL;
++    }
++  return 0;
++}
++
++static int
++node_expand_vars (struct wordsplit *wsp, struct wordsplit_node *node)
++{
++  const char *str = wsnode_ptr (wsp, node);
++  size_t slen = wsnode_len (node);
++  const char *end = str + slen;
++  const char *p;
++  size_t off = 0;
++  struct wordsplit_node *tail = node;
++
++  for (p = str; p < end; p++)
++    {
++      if (*p == '\\')
++	{
++	  p++;
++	  continue;
++	}
++      if (*p == '$')
++	{
++	  size_t n = p - str;
++
++	  if (tail != node)
++	    tail->flags |= _WSNF_JOIN;
++	  if (node_split_prefix (wsp, &tail, node, off, n, _WSNF_JOIN))
++	    return 1;
++	  p++;
++	  if (expvar (wsp, p, slen - n, &tail, &p,
++		      node->flags & (_WSNF_JOIN | _WSNF_QUOTE)))
++	    return 1;
++	  off += p - str + 1;
++	  str = p + 1;
++	}
++    }
++  if (p > str)
++    {
++      if (tail != node)
++	tail->flags |= _WSNF_JOIN;
++      if (node_split_prefix (wsp, &tail, node, off, p - str,
++			     node->flags & _WSNF_JOIN))
++	return 1;
++    }
++  if (tail != node)
++    {
++      wsnode_remove (wsp, node);
++      wsnode_free (node);
++    }
++  return 0;
++}
++
++/* Remove NULL lists */
++static void
++wsnode_nullelim (struct wordsplit *wsp)
++{
++  struct wordsplit_node *p;
++
++  for (p = wsp->ws_head; p;)
++    {
++      struct wordsplit_node *next = p->next;
++      if (p->flags & _WSNF_NULL)
++	{
++	  wsnode_remove (wsp, p);
++	  wsnode_free (p);
++	}
++      p = next;
++    }
++}
++
++static int
++wordsplit_varexp (struct wordsplit *wsp)
++{
++  struct wordsplit_node *p;
++
++  for (p = wsp->ws_head; p;)
++    {
++      struct wordsplit_node *next = p->next;
++      if (!(p->flags & _WSNF_NOEXPAND))
++	if (node_expand_vars (wsp, p))
++	  return 1;
++      p = next;
++    }
++
++  wsnode_nullelim (wsp);
++  return 0;
++}
++
++/* Strip off any leading and trailing whitespace.  This function is called
++   right after the initial scanning, therefore it assumes that every
++   node in the list is a text reference node. */
++static void
++wordsplit_trimws (struct wordsplit *wsp)
++{
++  struct wordsplit_node *p;
++
++  for (p = wsp->ws_head; p; p = p->next)
++    {
++      size_t n;
++
++      if (p->flags & _WSNF_QUOTE)
++	continue;
++
++      /* Skip leading whitespace: */
++      for (n = p->v.segm.beg; n < p->v.segm.end && ISWS (wsp->ws_input[n]);
++	   n++)
++	;
++      p->v.segm.beg = n;
++      /* Trim trailing whitespace */
++      for (n = p->v.segm.end;
++	   n > p->v.segm.beg && ISWS (wsp->ws_input[n - 1]); n--);
++      p->v.segm.end = n;
++      if (p->v.segm.beg == p->v.segm.end)
++	p->flags |= _WSNF_NULL;
++    }
++
++  wsnode_nullelim (wsp);
++}
++
++static int
++skip_sed_expr (const char *command, size_t i, size_t len)
++{
++  int state;
++
++  do
++    {
++      int delim;
++
++      if (command[i] == ';')
++	i++;
++      if (!(command[i] == 's' && i + 3 < len && ISPUNCT (command[i + 1])))
++	break;
++
++      delim = command[++i];
++      state = 1;
++      for (i++; i < len; i++)
++	{
++	  if (state == 3)
++	    {
++	      if (command[i] == delim || !ISALNUM (command[i]))
++		break;
++	    }
++	  else if (command[i] == '\\')
++	    i++;
++	  else if (command[i] == delim)
++	    state++;
++	}
++    }
++  while (state == 3 && i < len && command[i] == ';');
++  return i;
++}
++
++static size_t
++skip_delim (struct wordsplit *wsp)
++{
++  size_t start = wsp->ws_endp;
++  if (wsp->ws_flags & WRDSF_SQUEEZE_DELIMS)
++    {
++      if ((wsp->ws_flags & WRDSF_RETURN_DELIMS) &&
++	  ISDELIM (wsp, wsp->ws_input[start]))
++	{
++	  int delim = wsp->ws_input[start];
++	  do
++	    start++;
++	  while (start < wsp->ws_len && delim == wsp->ws_input[start]);
++	}
++      else
++	{
++	  do
++	    start++;
++	  while (start < wsp->ws_len && ISDELIM (wsp, wsp->ws_input[start]));
++	}
++      start--;
++    }
++
++  if (!(wsp->ws_flags & WRDSF_RETURN_DELIMS))
++    start++;
++
++  return start;
++}
++
++#define _WRDS_EOF   0
++#define _WRDS_OK    1
++#define _WRDS_ERR   2
++
++static int
++scan_qstring (struct wordsplit *wsp, size_t start, size_t * end)
++{
++  size_t j;
++  const char *command = wsp->ws_input;
++  size_t len = wsp->ws_len;
++  char q = command[start];
++
++  for (j = start + 1; j < len && command[j] != q; j++)
++    if (q == '"' && command[j] == '\\')
++      j++;
++  if (j < len && command[j] == q)
++    {
++      int flags = _WSNF_QUOTE | _WSNF_EMPTYOK;
++      if (q == '\'')
++	flags |= _WSNF_NOEXPAND;
++      if (wordsplit_add_segm (wsp, start + 1, j, flags))
++	return _WRDS_ERR;
++      *end = j;
++    }
++  else
++    {
++      wsp->ws_endp = start;
++      wsp->ws_errno = WRDSE_QUOTE;
++      if (wsp->ws_flags & WRDSF_SHOWERR)
++	wordsplit_perror (wsp);
++      return _WRDS_ERR;
++    }
++  return 0;
++}
++
++static int
++scan_word (struct wordsplit *wsp, size_t start)
++{
++  size_t len = wsp->ws_len;
++  const char *command = wsp->ws_input;
++  const char *comment = wsp->ws_comment;
++  int join = 0;
++  int flags = 0;
++
++  size_t i = start;
++
++  if (i >= len)
++    {
++      wsp->ws_errno = WRDSE_EOF;
++      return _WRDS_EOF;
++    }
++
++  start = i;
++
++  if (wsp->ws_flags & WRDSF_SED_EXPR
++      && command[i] == 's' && i + 3 < len && ISPUNCT (command[i + 1]))
++    {
++      flags = _WSNF_SEXP;
++      i = skip_sed_expr (command, i, len);
++    }
++  else if (!ISDELIM (wsp, command[i]))
++    {
++      while (i < len)
++	{
++	  if (comment && strchr (comment, command[i]) != NULL)
++	    {
++	      size_t j;
++	      for (j = i + 1; j < len && command[j] != '\n'; j++)
++		;
++	      if (wordsplit_add_segm (wsp, start, i, 0))
++		return _WRDS_ERR;
++	      wsp->ws_endp = j;
++	      return _WRDS_OK;
++	    }
++
++	  if (wsp->ws_flags & WRDSF_QUOTE)
++	    {
++	      if (command[i] == '\\')
++		{
++		  if (++i == len)
++		    break;
++		  i++;
++		  continue;
++		}
++
++	      if (((wsp->ws_flags & WRDSF_SQUOTE) && command[i] == '\'') ||
++		  ((wsp->ws_flags & WRDSF_DQUOTE) && command[i] == '"'))
++		{
++		  if (join && wsp->ws_tail)
++		    wsp->ws_tail->flags |= _WSNF_JOIN;
++		  if (wordsplit_add_segm (wsp, start, i, _WSNF_JOIN))
++		    return _WRDS_ERR;
++		  if (scan_qstring (wsp, i, &i))
++		    return _WRDS_ERR;
++		  start = i + 1;
++		  join = 1;
++		}
++	    }
++
++	  if (ISDELIM (wsp, command[i]))
++	    break;
++	  else
++	    i++;
++	}
++    }
++  else if (wsp->ws_flags & WRDSF_RETURN_DELIMS)
++    {
++      i++;
++    }
++  else if (!(wsp->ws_flags & WRDSF_SQUEEZE_DELIMS))
++    flags |= _WSNF_EMPTYOK;
++
++  if (join && i > start && wsp->ws_tail)
++    wsp->ws_tail->flags |= _WSNF_JOIN;
++  if (wordsplit_add_segm (wsp, start, i, flags))
++    return _WRDS_ERR;
++  wsp->ws_endp = i;
++  if (wsp->ws_flags & WRDSF_INCREMENTAL)
++    return _WRDS_EOF;
++  return _WRDS_OK;
++}
++
++static char quote_transtab[] = "\\\\\"\"a\ab\bf\fn\nr\rt\tv\v";
++
++int
++wordsplit_c_unquote_char (int c)
++{
++  char *p;
++
++  for (p = quote_transtab; *p; p += 2)
++    {
++      if (*p == c)
++	return p[1];
++    }
++  return c;
++}
++
++int
++wordsplit_c_quote_char (int c)
++{
++  char *p;
++
++  for (p = quote_transtab + sizeof (quote_transtab) - 2;
++       p > quote_transtab; p -= 2)
++    {
++      if (*p == c)
++	return p[-1];
++    }
++  return -1;
++}
++
++#define to_num(c) \
++  (ISDIGIT(c) ? c - '0' : (ISXDIGIT(c) ? toupper(c) - 'A' + 10 : 255 ))
++
++static int
++xtonum (int *pval, const char *src, int base, int cnt)
++{
++  int i, val;
++
++  for (i = 0, val = 0; i < cnt; i++, src++)
++    {
++      int n = *(unsigned char *) src;
++      if (n > 127 || (n = to_num (n)) >= base)
++	break;
++      val = val * base + n;
++    }
++  *pval = val;
++  return i;
++}
++
++size_t
++wordsplit_c_quoted_length (const char *str, int quote_hex, int *quote)
++{
++  size_t len = 0;
++
++  *quote = 0;
++  for (; *str; str++)
++    {
++      if (strchr (" \"", *str))
++	*quote = 1;
++
++      if (*str == ' ')
++	len++;
++      else if (*str == '"')
++	len += 2;
++      else if (*str != '\t' && *str != '\\' && ISPRINT (*str))
++	len++;
++      else if (quote_hex)
++	len += 3;
++      else
++	{
++	  if (wordsplit_c_quote_char (*str) != -1)
++	    len += 2;
++	  else
++	    len += 4;
++	}
++    }
++  return len;
++}
++
++void
++wordsplit_general_unquote_copy (char *dst, const char *src, size_t n,
++				   const char *escapable)
++{
++  int i;
++
++  for (i = 0; i < n;)
++    {
++      if (src[i] == '\\' && i < n && strchr (escapable, src[i + 1]))
++	i++;
++      *dst++ = src[i++];
++    }
++  *dst = 0;
++}
++
++void
++wordsplit_sh_unquote_copy (char *dst, const char *src, size_t n)
++{
++  int i;
++
++  for (i = 0; i < n;)
++    {
++      if (src[i] == '\\')
++	i++;
++      *dst++ = src[i++];
++    }
++  *dst = 0;
++}
++
++void
++wordsplit_c_unquote_copy (char *dst, const char *src, size_t n)
++{
++  int i = 0;
++  int c;
++
++  while (i < n)
++    {
++      if (src[i] == '\\')
++	{
++	  ++i;
++	  if (src[i] == 'x' || src[i] == 'X')
++	    {
++	      if (n - i < 2)
++		{
++		  *dst++ = '\\';
++		  *dst++ = src[i++];
++		}
++	      else
++		{
++		  int off = xtonum (&c, src + i + 1,
++				    16, 2);
++		  if (off == 0)
++		    {
++		      *dst++ = '\\';
++		      *dst++ = src[i++];
++		    }
++		  else
++		    {
++		      *dst++ = c;
++		      i += off + 1;
++		    }
++		}
++	    }
++	  else if ((unsigned char) src[i] < 128 && ISDIGIT (src[i]))
++	    {
++	      if (n - i < 1)
++		{
++		  *dst++ = '\\';
++		  *dst++ = src[i++];
++		}
++	      else
++		{
++		  int off = xtonum (&c, src + i, 8, 3);
++		  if (off == 0)
++		    {
++		      *dst++ = '\\';
++		      *dst++ = src[i++];
++		    }
++		  else
++		    {
++		      *dst++ = c;
++		      i += off;
++		    }
++		}
++	    }
++	  else
++	    *dst++ = wordsplit_c_unquote_char (src[i++]);
++	}
++      else
++	*dst++ = src[i++];
++    }
++  *dst = 0;
++}
++
++void
++wordsplit_c_quote_copy (char *dst, const char *src, int quote_hex)
++{
++  for (; *src; src++)
++    {
++      if (*src == '"')
++	{
++	  *dst++ = '\\';
++	  *dst++ = *src;
++	}
++      else if (*src != '\t' && *src != '\\' && ISPRINT (*src))
++	*dst++ = *src;
++      else
++	{
++	  char tmp[4];
++
++	  if (quote_hex)
++	    {
++	      snprintf (tmp, sizeof tmp, "%%%02X", *(unsigned char *) src);
++	      memcpy (dst, tmp, 3);
++	      dst += 3;
++	    }
++	  else
++	    {
++	      int c = wordsplit_c_quote_char (*src);
++	      *dst++ = '\\';
++	      if (c != -1)
++		*dst++ = c;
++	      else
++		{
++		  snprintf (tmp, sizeof tmp, "%03o", *(unsigned char *) src);
++		  memcpy (dst, tmp, 3);
++		  dst += 3;
++		}
++	    }
++	}
++    }
++}
++
++static int
++wordsplit_process_list (struct wordsplit *wsp, size_t start)
++{
++  if (wsp->ws_flags & WRDSF_NOSPLIT)
++    {
++      /* Treat entire input as a quoted argument */
++      if (wordsplit_add_segm (wsp, start, wsp->ws_len, _WSNF_QUOTE))
++	return wsp->ws_errno;
++    }
++  else
++    {
++      int rc;
++
++      while ((rc = scan_word (wsp, start)) == _WRDS_OK)
++	start = skip_delim (wsp);
++      /* Make sure tail element is not joinable */
++      if (wsp->ws_tail)
++	wsp->ws_tail->flags &= ~_WSNF_JOIN;
++      if (rc == _WRDS_ERR)
++	return wsp->ws_errno;
++    }
++
++  if (wsp->ws_flags & WRDSF_SHOWDBG)
++    {
++      wsp->ws_debug ("Initial list:");
++      wordsplit_dump_nodes (wsp);
++    }
++
++  if (wsp->ws_flags & WRDSF_WS)
++    {
++      /* Trim leading and trailing whitespace */
++      wordsplit_trimws (wsp);
++      if (wsp->ws_flags & WRDSF_SHOWDBG)
++	{
++	  wsp->ws_debug ("After WS trimming:");
++	  wordsplit_dump_nodes (wsp);
++	}
++    }
++
++  /* Expand variables (FIXME: & commands) */
++  if (!(wsp->ws_flags & WRDSF_NOVAR))
++    {
++      if (wordsplit_varexp (wsp))
++	{
++	  wordsplit_free_nodes (wsp);
++	  return wsp->ws_errno;
++	}
++      if (wsp->ws_flags & WRDSF_SHOWDBG)
++	{
++	  wsp->ws_debug ("Expanded list:");
++	  wordsplit_dump_nodes (wsp);
++	}
++    }
++
++  do
++    {
++      if (wsnode_quoteremoval (wsp))
++	break;
++      if (wsp->ws_flags & WRDSF_SHOWDBG)
++	{
++	  wsp->ws_debug ("After quote removal:");
++	  wordsplit_dump_nodes (wsp);
++	}
++
++      if (wsnode_coalesce (wsp))
++	break;
++
++      if (wsp->ws_flags & WRDSF_SHOWDBG)
++	{
++	  wsp->ws_debug ("Coalesced list:");
++	  wordsplit_dump_nodes (wsp);
++	}
++    }
++  while (0);
++  return wsp->ws_errno;
++}
++
++int
++wordsplit_len (const char *command, size_t length, struct wordsplit *wsp, 
++               int flags)
++{
++  int rc;
++  size_t start;
++  const char *cmdptr;
++  size_t cmdlen;
++
++  if (!command)
++    {
++      if (!(flags & WRDSF_INCREMENTAL))
++	return EINVAL;
++
++      start = skip_delim (wsp);
++      if (wsp->ws_endp == wsp->ws_len)
++	{
++	  wsp->ws_errno = WRDSE_NOINPUT;
++	  if (wsp->ws_flags & WRDSF_SHOWERR)
++	    wordsplit_perror (wsp);
++	  return wsp->ws_errno;
++	}
++
++      cmdptr = wsp->ws_input + wsp->ws_endp;
++      cmdlen = wsp->ws_len - wsp->ws_endp;
++      wsp->ws_flags |= WRDSF_REUSE;
++      wordsplit_init0 (wsp);
++    }
++  else
++    {
++      cmdptr = command;
++      cmdlen = length;
++      start = 0;
++      rc = wordsplit_init (wsp, cmdptr, cmdlen, flags);
++      if (rc)
++	return rc;
++    }
++
++  if (wsp->ws_flags & WRDSF_SHOWDBG)
++    wsp->ws_debug ("Input:%.*s;", (int) cmdlen, cmdptr);
++
++  rc = wordsplit_process_list (wsp, start);
++  if (rc == 0 && (flags & WRDSF_INCREMENTAL))
++    {
++      while (!wsp->ws_head && wsp->ws_endp < wsp->ws_len)
++	{
++	  start = skip_delim (wsp);
++	  if (wsp->ws_flags & WRDSF_SHOWDBG)
++	    {
++	      cmdptr = wsp->ws_input + wsp->ws_endp;
++	      cmdlen = wsp->ws_len - wsp->ws_endp;
++	      wsp->ws_debug ("Restart:%.*s;", (int) cmdlen, cmdptr);
++	    }
++	  rc = wordsplit_process_list (wsp, start);
++	  if (rc)
++	    break;
++	}
++    }
++  if (rc)
++    {
++      wordsplit_free_nodes (wsp);
++      return rc;
++    }
++  wordsplit_finish (wsp);
++  wordsplit_free_nodes (wsp);
++  return wsp->ws_errno;
++}
++
++int
++wordsplit (const char *command, struct wordsplit *ws, int flags)
++{
++  return wordsplit_len (command, command ? strlen (command) : 0, ws,
++			   flags);
++}
++
++void
++wordsplit_free_words (struct wordsplit *ws)
++{
++  size_t i;
++
++  for (i = 0; i < ws->ws_wordc; i++)
++    {
++      char *p = ws->ws_wordv[ws->ws_offs + i];
++      if (p)
++	{
++	  free (p);
++	  ws->ws_wordv[ws->ws_offs + i] = NULL;
++	}
++    }
++  ws->ws_wordc = 0;
++}
++
++void
++wordsplit_free (struct wordsplit *ws)
++{
++  wordsplit_free_words (ws);
++  free (ws->ws_wordv);
++  ws->ws_wordv = NULL;
++}
++
++void
++wordsplit_perror (struct wordsplit *wsp)
++{
++  switch (wsp->ws_errno)
++    {
++    case WRDSE_EOF:
++      wsp->ws_error (_("no error"));
++      break;
++
++    case WRDSE_QUOTE:
++      wsp->ws_error (_("missing closing %c (start near #%lu)"),
++		     wsp->ws_input[wsp->ws_endp],
++		     (unsigned long) wsp->ws_endp);
++      break;
++
++    case WRDSE_NOSPACE:
++      wsp->ws_error (_("memory exhausted"));
++      break;
++
++    case WRDSE_NOSUPP:
++      wsp->ws_error (_("command substitution is not yet supported"));
++
++    case WRDSE_USAGE:
++      wsp->ws_error (_("invalid wordsplit usage"));
++      break;
++
++    case WRDSE_CBRACE:
++      wsp->ws_error (_("unbalanced curly brace"));
++      break;
++
++    case WRDSE_UNDEF:
++      wsp->ws_error (_("undefined variable"));
++      break;
++
++    case WRDSE_NOINPUT:
++      wsp->ws_error (_("input exhausted"));
++      break;
++
++    default:
++      wsp->ws_error (_("unknown error"));
++    }
++}
++
++const char *_wordsplit_errstr[] = {
++  N_("no error"),
++  N_("missing closing quote"),
++  N_("memory exhausted"),
++  N_("command substitution is not yet supported"),
++  N_("invalid wordsplit usage"),
++  N_("unbalanced curly brace"),
++  N_("undefined variable"),
++  N_("input exhausted")
++};
++int _wordsplit_nerrs =
++  sizeof (_wordsplit_errstr) / sizeof (_wordsplit_errstr[0]);
++
++const char *
++wordsplit_strerror (struct wordsplit *ws)
++{
++  if (ws->ws_errno < _wordsplit_nerrs)
++    return _wordsplit_errstr[ws->ws_errno];
++  return N_("unknown error");
++}
+diff --git a/lib/wordsplit.h b/lib/wordsplit.h
+new file mode 100644
+index 0000000..b48e3cd
+--- /dev/null
++++ b/lib/wordsplit.h
+@@ -0,0 +1,162 @@
++/* wordsplit - a word splitter
++   Copyright (C) 2009-2013 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 <http://www.gnu.org/licenses/>.
++
++   Written by Sergey Poznyakoff
++*/
++
++#ifndef __WORDSPLIT_H
++#define __WORDSPLIT_H
++
++#include <stddef.h>
++
++struct wordsplit
++{
++  size_t ws_wordc;
++  char **ws_wordv;
++  size_t ws_offs;
++  size_t ws_wordn;
++  int ws_flags;
++  const char *ws_delim;
++  const char *ws_comment;
++  const char *ws_escape;
++  void (*ws_alloc_die) (struct wordsplit * wsp);
++  void (*ws_error) (const char *, ...)
++                   __attribute__ ((__format__ (__printf__, 1, 2)));
++  void (*ws_debug) (const char *, ...)
++                   __attribute__ ((__format__ (__printf__, 1, 2)));
++
++  const char **ws_env;
++  const char *(*ws_getvar) (const char *, size_t, void *);
++  void *ws_closure;
++
++  const char *ws_input;
++  size_t ws_len;
++  size_t ws_endp;
++  int ws_errno;
++  struct wordsplit_node *ws_head, *ws_tail;
++};
++
++/* Wordsplit flags.  Only 2 bits of a 32-bit word remain unused.
++   It is getting crowded... */
++/* Append the words found to the array resulting from a previous
++   call. */
++#define WRDSF_APPEND            0x00000001
++/* Insert we_offs initial NULLs in the array ws_wordv.
++   (These are not counted in the returned ws_wordc.) */
++#define WRDSF_DOOFFS            0x00000002
++/* Don't do command substitution. Reserved for future use. */
++#define WRDSF_NOCMD             0x00000004
++/* The parameter p resulted from a previous call to
++   wordsplit(), and wordsplit_free() was not called. Reuse the
++   allocated storage. */
++#define WRDSF_REUSE             0x00000008
++/* Print errors */
++#define WRDSF_SHOWERR           0x00000010
++/* Consider it an error if an undefined shell variable
++   is expanded. */
++#define WRDSF_UNDEF             0x00000020
++
++/* Don't do variable expansion. */
++#define WRDSF_NOVAR             0x00000040
++/* Abort on ENOMEM error */
++#define WRDSF_ENOMEMABRT        0x00000080
++/* Trim off any leading and trailind whitespace */
++#define WRDSF_WS                0x00000100
++/* Handle single quotes */
++#define WRDSF_SQUOTE            0x00000200
++/* Handle double quotes */
++#define WRDSF_DQUOTE            0x00000400
++/* Handle quotes and escape directives */
++#define WRDSF_QUOTE             (WRDSF_SQUOTE|WRDSF_DQUOTE)
++/* Replace each input sequence of repeated delimiters with a single
++   delimiter */
++#define WRDSF_SQUEEZE_DELIMS    0x00000800
++/* Return delimiters */
++#define WRDSF_RETURN_DELIMS     0x00001000
++/* Treat sed expressions as words */
++#define WRDSF_SED_EXPR          0x00002000
++/* ws_delim field is initialized */
++#define WRDSF_DELIM             0x00004000
++/* ws_comment field is initialized */
++#define WRDSF_COMMENT           0x00008000
++/* ws_alloc_die field is initialized */
++#define WRDSF_ALLOC_DIE         0x00010000
++/* ws_error field is initialized */
++#define WRDSF_ERROR             0x00020000
++/* ws_debug field is initialized */
++#define WRDSF_DEBUG             0x00040000
++/* ws_env field is initialized */
++#define WRDSF_ENV               0x00080000
++/* ws_getvar field is initialized */
++#define WRDSF_GETVAR            0x00100000
++/* enable debugging */
++#define WRDSF_SHOWDBG           0x00200000
++/* Don't split input into words.  Useful for side effects. */
++#define WRDSF_NOSPLIT           0x00400000
++/* Keep undefined variables in place, instead of expanding them to
++   empty string */
++#define WRDSF_KEEPUNDEF         0x00800000
++/* Warn about undefined variables */
++#define WRDSF_WARNUNDEF         0x01000000
++/* Handle C escapes */
++#define WRDSF_CESCAPES          0x02000000
++
++/* ws_closure is set */
++#define WRDSF_CLOSURE           0x04000000
++/* ws_env is a Key/Value environment, i.e. the value of a variable is
++   stored in the element that follows its name. */
++#define WRDSF_ENV_KV            0x08000000
++
++/* ws_escape is set */
++#define WRDSF_ESCAPE            0x10000000
++
++/* Incremental mode */
++#define WRDSF_INCREMENTAL       0x20000000
++
++#define WRDSF_DEFFLAGS	       \
++  (WRDSF_NOVAR | WRDSF_NOCMD | \
++   WRDSF_QUOTE | WRDSF_SQUEEZE_DELIMS | WRDSF_CESCAPES)
++
++#define WRDSE_EOF        0
++#define WRDSE_QUOTE      1
++#define WRDSE_NOSPACE    2
++#define WRDSE_NOSUPP     3
++#define WRDSE_USAGE      4
++#define WRDSE_CBRACE     5
++#define WRDSE_UNDEF      6
++#define WRDSE_NOINPUT    7
++
++int wordsplit (const char *s, struct wordsplit *p, int flags);
++int wordsplit_len (const char *s, size_t len,
++		      struct wordsplit *p, int flags);
++void wordsplit_free (struct wordsplit *p);
++void wordsplit_free_words (struct wordsplit *ws);
++
++int wordsplit_c_unquote_char (int c);
++int wordsplit_c_quote_char (int c);
++size_t wordsplit_c_quoted_length (const char *str, int quote_hex,
++				  int *quote);
++void wordsplit_general_unquote_copy (char *dst, const char *src, size_t n,
++				     const char *escapable);
++void wordsplit_sh_unquote_copy (char *dst, const char *src, size_t n);
++void wordsplit_c_unquote_copy (char *dst, const char *src, size_t n);
++void wordsplit_c_quote_copy (char *dst, const char *src, int quote_hex);
++
++void wordsplit_perror (struct wordsplit *ws);
++const char *wordsplit_strerror (struct wordsplit *ws);
++
++
++#endif
+diff --git a/src/system.c b/src/system.c
+index ea88cd6..a27a7de 100644
+--- a/src/system.c
++++ b/src/system.c
+@@ -23,6 +23,20 @@
+ #include <priv-set.h>
+ #include <rmt.h>
+ #include <signal.h>
++#include <wordsplit.h>
++
++static void
++xexec (const char *cmd)
++{
++  struct wordsplit ws;
++
++  ws.ws_env = (const char **) environ;
++  if (wordsplit (cmd, &ws, (WRDSF_DEFFLAGS | WRDSF_ENV) & ~WRDSF_NOVAR))
++    FATAL_ERROR ((0, 0, _("cannot split string '%s': %s"),
++		  cmd, wordsplit_strerror (&ws)));
++  execvp (ws.ws_wordv[0], ws.ws_wordv);
++  exec_fatal (cmd);
++}
+ 
+ #if MSDOS
+ 
+@@ -194,7 +208,7 @@ sys_spawn_shell (void)
+   if (child == 0)
+     {
+       priv_set_restore_linkdir ();
+-      execlp (shell, "-sh", "-i", (char *) 0);
++      execlp (shell, "-sh", "-i", NULL);
+       exec_fatal (shell);
+     }
+   else
+@@ -334,7 +348,7 @@ sys_child_open_for_compress (void)
+   int child_pipe[2];
+   pid_t grandchild_pid;
+   pid_t child_pid;
+-
++  
+   xpipe (parent_pipe);
+   child_pid = xfork ();
+ 
+@@ -382,8 +396,7 @@ sys_child_open_for_compress (void)
+ 	  xdup2 (archive, STDOUT_FILENO);
+ 	}
+       priv_set_restore_linkdir ();
+-      execlp (use_compress_program_option, use_compress_program_option, NULL);
+-      exec_fatal (use_compress_program_option);
++      xexec (use_compress_program_option);
+     }
+ 
+   /* We do need a grandchild tar.  */
+@@ -400,9 +413,7 @@ sys_child_open_for_compress (void)
+       xdup2 (child_pipe[PWRITE], STDOUT_FILENO);
+       xclose (child_pipe[PREAD]);
+       priv_set_restore_linkdir ();
+-      execlp (use_compress_program_option, use_compress_program_option,
+-	      (char *) 0);
+-      exec_fatal (use_compress_program_option);
++      xexec (use_compress_program_option);
+     }
+ 
+   /* The child tar is still here!  */
+@@ -477,7 +488,12 @@ run_decompress_program (void)
+ {
+   int i;
+   const char *p, *prog = NULL;
++  struct wordsplit ws;
++  int wsflags = (WRDSF_DEFFLAGS | WRDSF_ENV | WRDSF_DOOFFS) & ~WRDSF_NOVAR;
+ 
++  ws.ws_env = (const char **) environ;
++  ws.ws_offs = 1;
++  
+   for (p = first_decompress_program (&i); p; p = next_decompress_program (&i))
+     {
+       if (prog)
+@@ -487,8 +503,16 @@ run_decompress_program (void)
+ 	  WARNOPT (WARN_DECOMPRESS_PROGRAM,
+ 		   (0, 0, _("trying %s"), p));
+ 	}
+-      prog = p;
+-      execlp (p, p, "-d", NULL);
++      if (wordsplit (p, &ws, wsflags))
++	FATAL_ERROR ((0, 0, _("cannot split string '%s': %s"),
++		      p, wordsplit_strerror (&ws)));
++      wsflags |= WRDSF_REUSE;
++      memmove(ws.ws_wordv, ws.ws_wordv + ws.ws_offs,
++	      sizeof(ws.ws_wordv[0])*ws.ws_wordc);
++      ws.ws_wordv[ws.ws_wordc] = "-d";
++      prog = p;		      
++      execvp (ws.ws_wordv[0], ws.ws_wordv);
++      ws.ws_wordv[ws.ws_wordc] = NULL;
+     }
+   if (!prog)
+     FATAL_ERROR ((0, 0, _("unable to run decompression program")));
+@@ -722,7 +746,7 @@ sys_exec_command (char *file_name, int typechar, struct tar_stat_info *st)
+ {
+   int p[2];
+   char *argv[4];
+-
++  
+   xpipe (p);
+   pipe_handler = signal (SIGPIPE, SIG_IGN);
+   global_pid = xfork ();
+@@ -739,15 +763,8 @@ sys_exec_command (char *file_name, int typechar, struct tar_stat_info *st)
+ 
+   stat_to_env (file_name, typechar, st);
+ 
+-  argv[0] = "/bin/sh";
+-  argv[1] = "-c";
+-  argv[2] = to_command_option;
+-  argv[3] = NULL;
+-
+   priv_set_restore_linkdir ();
+-  execv ("/bin/sh", argv);
+-
+-  exec_fatal (file_name);
++  xexec (to_command_option);
+ }
+ 
+ void
+@@ -854,15 +871,8 @@ sys_exec_info_script (const char **archive_name, int volume_number)
+ 
+   xclose (p[PREAD]);
+ 
+-  argv[0] = "/bin/sh";
+-  argv[1] = "-c";
+-  argv[2] = (char *) info_script_option;
+-  argv[3] = NULL;
+-
+   priv_set_restore_linkdir ();
+-  execv (argv[0], argv);
+-
+-  exec_fatal (info_script_option);
++  xexec (info_script_option);
+ }
+ 
+ void
+@@ -908,9 +918,7 @@ sys_exec_checkpoint_script (const char *script_name,
+   argv[3] = NULL;
+ 
+   priv_set_restore_linkdir ();
+-  execv (argv[0], argv);
+-
+-  exec_fatal (script_name);
++  xexec (script_name);
+ }
+ 
+ #endif /* not MSDOS */
diff --git a/tar.spec b/tar.spec
index 0711ead..cd23213 100644
--- a/tar.spec
+++ b/tar.spec
@@ -88,6 +88,12 @@ Patch12: tar-1.26-pax-big-sparse-files.patch
 # ~> upstream (beca89bc)
 Patch13: tar-1.26-allow-extract-single-volume.patch
 
+# Allow to pass arguments to commands called from tar
+# ~> resolves #819187 (now we can use ./configure --with-lzma)
+# ~> http://lists.gnu.org/archive/html/bug-tar/2013-02/msg00003.html
+# ~> upstream: 7b5e803963
+Patch14: tar-1.26-command-args.patch
+
 # run "make check" by default
 %bcond_without check
 
@@ -134,6 +140,7 @@ the rmt package.
 %patch11 -p1 -b .big_uid_gid
 %patch12 -p1 -b .pax-sparse-big-files
 %patch13 -p1 -b .extract-single-volume
+%patch14 -p1 -b .command-args
 
 autoreconf -v
 
@@ -142,7 +149,8 @@ autoreconf -v
 %global CONFIGURE_SELINUX --without-selinux
 %endif
 
-%configure %{?CONFIGURE_SELINUX}
+%configure %{?CONFIGURE_SELINUX} \
+    --with-lzma="xz --format=lzma"
 make
 
 %install
@@ -192,6 +200,7 @@ fi
 * Tue Mar 19 2013 Pavel Raiskup <praiskup at redhat.com> - 2:1.26-20
 - allow extracting single volume from multi-volume archive (#919897)
 - usrmove: /bin/tar ~> /usr/bin/tar, selinux handling edit
+- add possibility to pass arguments to commands called from tar (#819187)
 
 * Fri Mar 01 2013 Pavel Raiskup <praiskup at redhat.com> - 2:1.26-19
 - fix creating sparse pax archives containing files of effective


More information about the scm-commits mailing list