Pass command line arguments to external commands.

Any option taking a command name as its argument accepts
additional arguments as well.

* lib/wordsplit.c: New file.
* lib/wordsplit.h: New file.
* lib/Makefile.am: Add new files.
* src/system.c (xexec): New function.
(run_decompress_program): Use wordsplit.
(sys_child_open_for_compress,sys_exec_command)
(sys_exec_info_script)
(sys_exec_checkpoint_script): Use xexec to invoke external
command.

* NEWS: Update.
* doc/tar.texi: Update.
This commit is contained in:
Sergey Poznyakoff
2013-02-10 14:40:23 +02:00
parent 20dcc4d122
commit 7b5e803963
6 changed files with 1990 additions and 78 deletions

20
NEWS
View File

@@ -1,4 +1,4 @@
GNU tar NEWS - User visible changes. 2013-01-26
GNU tar NEWS - User visible changes. 2013-02-10
Please send GNU tar bug reports to <bug-tar@gnu.org>
@@ -50,6 +50,24 @@ Additionally, the options --xattrs-include and --xattrs-exclude allow
you to selectively control for which files to store (or extract) the
extended attributes.
* 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'
* New configure option --enable-gcc-warnings, intended for debugging.

View File

@@ -1800,13 +1800,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
@@ -2784,14 +2785,14 @@ Send verbose output to @var{file} instead of to standard output.
@opsummary{info-script}
@opsummary{new-volume-script}
@item --info-script=@var{script-file}
@itemx --new-volume-script=@var{script-file}
@itemx -F @var{script-file}
@item --info-script=@var{command}
@itemx --new-volume-script=@var{command}
@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,
@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,
@command{tar} fails immediately. @xref{info-script}, for a detailed
discussion of this feature.
@opsummary{interactive}
@item --interactive
@@ -4004,17 +4005,22 @@ checkpoint:
$ @kbd{tar -c --checkpoint=1000 --checkpoint-action=sleep=30}
@end smallexample
@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
@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
@@ -4044,6 +4050,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:
@smallexample
@kbd{tar -c -f arc.tar \
--checkpoint-action='exec=/sbin/cpoint $TAR_FILENAME'}
@end smallexample
@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
@@ -4258,6 +4276,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.
@node external
@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
@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
@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:
@smallexample
$ @kbd{tar -x -f archive.tar \
--checkpoint=exec='printf "%04d in %32s\r" $TAR_CHECKPOINT $TAR_ARCHIVE'}
@end smallexample
@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
@@ -5470,11 +5524,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
@var{command}. When this option is used, instead of creating the
@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
@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
@@ -5572,6 +5628,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
@var{proc} takes the member name and size as its arguments, then you
could do:
@smallexample
$ @kbd{tar -x -f archive.tar \
--to-command='proc $TAR_FILENAME $TAR_SIZE'}
@end smallexample
@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:
@@ -8932,9 +9002,15 @@ environment variable. For example, with @command{gzip} you can set
@smallexample
$ @kbd{GZIP='-9 -n' tar czf archive.tar.gz subdir}
@end smallexample
Another way would be to use the @option{-I} option instead (see
below), e.g.:
@smallexample
$ @kbd{tar -cf archive.tar.gz -I 'gzip -9 -n' subdir}
@end smallexample
@noindent
The traditional way to do this is to use a pipe:
Finally, the third, traditional, way to do this is to use a pipe:
@smallexample
$ @kbd{tar cf - subdir | gzip -9 -n > archive.tar.gz}
@@ -8977,20 +9053,29 @@ suffix. The following suffixes are recognized:
@item @samp{.xz} @tab @command{xz}
@end multitable
@anchor{use-compress-program}
@opindex use-compress-program
@item --use-compress-program=@var{prog}
@itemx -I=@var{prog}
Use external compression program @var{prog}. Use this option if you
@item --use-compress-program=@var{command}
@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. The program should follow two conventions:
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).
First, when invoked without options, it should read data from standard
input, compress it and output it on standard output.
The @var{command} should follow two conventions:
Secondly, if invoked with the @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.
First, when invoked without additional options, it should read data
from standard input, compress it and output it on 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
@@ -10462,10 +10547,10 @@ maximum tape length, you might avoid the problem entirely.
@xopindex{info-script, short description}
@xopindex{new-volume-script, short description}
@item -F @var{file}
@itemx --info-script=@var{file}
@itemx --new-volume-script=@var{file}
Execute @file{file} at end of each tape. This implies
@item -F @var{command}
@itemx --info-script=@var{command}
@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
@@ -11345,19 +11430,20 @@ volume, and instruct @command{tar} to use it instead of its normal
prompting procedure:
@table @option
@item --info-script=@var{script-name}
@itemx --new-volume-script=@var{script-name}
@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
@item --info-script=@var{command}
@itemx --new-volume-script=@var{command}
@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.
@xref{external, Running External Commands}, for a detailed discussion
of the way @GNUTAR{} runs external commands. It inherits
@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
@@ -11392,6 +11478,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).

View File

@@ -30,7 +30,15 @@ CLEANFILES = rmt-command.h rmt-command.h-t
AM_CPPFLAGS = -I$(top_srcdir)/gnu -I../ -I../gnu
AM_CFLAGS = $(GNULIB_WARN_CFLAGS) $(WERROR_CFLAGS)
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 \
@@ -38,6 +46,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

1625
lib/wordsplit.c Normal file

File diff suppressed because it is too large Load Diff

162
lib/wordsplit.h Normal file
View File

@@ -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

View File

@@ -21,6 +21,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
@@ -192,7 +206,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
@@ -315,7 +329,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 ();
@@ -363,8 +377,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. */
@@ -381,9 +394,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! */
@@ -458,7 +469,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)
@@ -468,8 +484,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")));
@@ -703,7 +727,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 ();
@@ -720,15 +744,8 @@ sys_exec_command (char *file_name, int typechar, struct tar_stat_info *st)
stat_to_env (file_name, typechar, st);
argv[0] = (char *) "/bin/sh";
argv[1] = (char *) "-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
@@ -832,18 +849,11 @@ sys_exec_info_script (const char **archive_name, int volume_number)
archive_format_string (current_format == DEFAULT_FORMAT ?
archive_format : current_format), 1);
setenv ("TAR_FD", STRINGIFY_BIGINT (p[PWRITE], uintbuf), 1);
xclose (p[PREAD]);
argv[0] = (char *) "/bin/sh";
argv[1] = (char *) "-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
@@ -854,7 +864,7 @@ sys_exec_checkpoint_script (const char *script_name,
pid_t pid;
char *argv[4];
char uintbuf[UINTMAX_STRSIZE_BOUND];
pid = xfork ();
if (pid != 0)
@@ -889,9 +899,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 */