Make sure the changes become visible with --show-transformed-names.
* src/common.h (strip_compression_suffix): New function.
(one_top_level): Rename to one_top_level_dir. All uses changed.
* src/extract.c (extr_init): Use strip_compression_suffix.
Bail out if unable to determine top-level directory.
(maybe_prepend_name): Remove. All uses removed.
* src/tar.c (options): --one-top-level takes optional argument.
(parse_opt): Handle it.
* src/list.c (enforce_one_top_level): New function.
(transform_stat_info): Call enforce_one_top_level if required.
* src/suffix.c (compression_suffixes): List "tar" (no compression);
terminate with NULL entry.
(find_compression_suffix): New static.
(strip_compression_suffix): New function.
* doc/tar.1: Update.
* doc/tar.texi: Update.
* tests/onetop01.at: New testcase.
* tests/onetop02.at: New testcase.
* tests/onetop03.at: New testcase.
* tests/Makefile.am: Add new testcases.
* tests/testsuite.at: Likewise.
.\"
.\" You should have received a copy of the GNU General Public License
.\" along with this program. If not, see <http://www.gnu.org/licenses/>.
-.TH TAR 1 "January 27, 2014" "TAR" "GNU TAR Manual"
+.TH TAR 1 "January 28, 2014" "TAR" "GNU TAR Manual"
.SH NAME
tar \- an archiving utility
.SH SYNOPSIS
\fB\-\-no\-overwrite\-dir\fR
Preserve metadata of existing directories.
.TP
+\fB\-\-one\-top\-level\fR[\fB=\fIDIR\fR]
+Extract all files into \fIDIR\fR, or, if used without argument, into a
+subdirectory named by the base name of the archive (minus standard
+compression suffixes recognizable by \fB\-\-auto\-compress).
+.TP
\fB\-\-overwrite\fR
Overwrite existing files when extracting.
.TP
directory.
@opsummary{one-top-level}
-@item --one-top-level
+@item --one-top-level[=@var{dir}]
Tells @command{tar} to create a new directory beneath the extraction directory
-(or the one passed to @option{-C}) and use it to guard against tarbombs. The
-name of the new directory will be equal to the name of the archive with the
-extension stripped off. If any archive names (after transformations from
-@option{--transform} and @option{--strip-components}) do not already begin with
-it, the new directory will be prepended to the names immediately before
-extraction. Recognized extensions are @samp{.tar}, @samp{.taz}, @samp{.tbz},
-@samp{.tb2}, @samp{.tgz}, @samp{.tlz} and @samp{.txz}.
+(or the one passed to @option{-C}) and use it to guard against
+tarbombs. In the absence of @var{dir} argument, the name of the new directory
+will be equal to the base name of the archive (file name minus the
+archive suffix, if recognized). Any member names that do not begin
+with that directory name (after
+transformations from @option{--transform} and
+@option{--strip-components}) will be prefixed with it. Recognized
+file name suffixes are @samp{.tar}, and any compression suffixes
+recognizable by @xref{--auto-compress}.
@opsummary{overwrite}
@item --overwrite
/* Create a top-level directory for extracting based on the archive name. */
GLOBAL bool one_top_level_option;
-GLOBAL char *one_top_level;
+GLOBAL char *one_top_level_dir;
/* Specified value to be put into tar file in place of stat () results, or
just null and -1 if such an override should not take place. */
/* Module suffix.c */
void set_compression_program_by_suffix (const char *name, const char *defprog);
+char *strip_compression_suffix (const char *name);
/* Module checkpoint.c */
void checkpoint_compile_action (const char *str);
/* If the user wants to guarantee that everything is under one directory,
determine its name now and let it be created later. */
- if (one_top_level_option)
+ if (one_top_level_option && !one_top_level_dir)
{
- int i;
char *base = base_name (archive_name_array[0]);
- for (i = strlen (base) - 1; i > 2; i--)
- if (!strncmp (base + i - 3, ".tar", 4) ||
- !strncmp (base + i - 3, ".taz", 4) ||
- !strncmp (base + i - 3, ".tbz", 4) ||
- !strncmp (base + i - 3, ".tb2", 4) ||
- !strncmp (base + i - 3, ".tgz", 4) ||
- !strncmp (base + i - 3, ".tlz", 4) ||
- !strncmp (base + i - 3, ".txz", 4)) break;
-
- if (i <= 3)
- {
- one_top_level_option = false;
- free (base);
- return;
- }
-
- one_top_level = xmalloc (i - 2);
- strncpy (one_top_level, base, i - 3);
- one_top_level[i - 3] = '\0';
+ one_top_level_dir = strip_compression_suffix (base);
free (base);
+
+ if (!one_top_level_dir)
+ USAGE_ERROR ((0, 0, _("Cannot deduce top-level directory name; please set it explicitly with --one-top-level=DIR")));
}
}
return 1;
}
-void
-maybe_prepend_name (char **file_name)
-{
- int i;
-
- for (i = 0; i < strlen (*file_name); i++)
- if (!ISSLASH ((*file_name)[i]) && (*file_name)[i] != '.') break;
-
- if (i == strlen (*file_name))
- return;
-
- if (!strncmp (*file_name + i, one_top_level, strlen (one_top_level)))
- {
- int pos = i + strlen (one_top_level);
- if (ISSLASH ((*file_name)[pos]) || (*file_name)[pos] == '\0') return;
- }
-
- char *new_name = xmalloc (strlen (one_top_level) + strlen (*file_name) + 2);
-
- strcpy (new_name, one_top_level);
- strcat (new_name, "/");
- strcat (new_name, *file_name);
-
- free (*file_name);
- *file_name = new_name;
-}
-
/* Extract a file from the archive. */
void
extract_archive (void)
typeflag = sparse_member_p (¤t_stat_info) ?
GNUTYPE_SPARSE : current_header->header.typeflag;
- if (one_top_level_option)
- maybe_prepend_name (¤t_stat_info.file_name);
-
if (prepare_to_extract (current_stat_info.file_name, typeflag, &fun))
{
if (fun && (*fun) (current_stat_info.file_name, typeflag)
return transform_name_fp (pinput, type, decode_xform, &type);
}
+static void
+enforce_one_top_level (char **pfile_name)
+{
+ char *file_name = *pfile_name;
+ char *p;
+
+ for (p = file_name; *p && (ISSLASH (*p) || *p == '.'); p++)
+ ;
+
+ if (!*p)
+ return;
+
+ if (strncmp (p, one_top_level_dir, strlen (one_top_level_dir)) == 0)
+ {
+ int pos = strlen (one_top_level_dir);
+ if (ISSLASH (p[pos]) || p[pos] == 0)
+ return;
+ }
+
+ *pfile_name = new_name (one_top_level_dir, file_name);
+ normalize_filename_x (*pfile_name);
+ free (file_name);
+}
+
void
transform_stat_info (int typeflag, struct tar_stat_info *stat_info)
{
case LNKTYPE:
transform_member_name (&stat_info->link_name, XFORM_LINK);
}
+
+ if (one_top_level_option)
+ enforce_one_top_level (¤t_stat_info.file_name);
}
/* Main loop for reading an archive. */
continue;
}
}
+
transform_stat_info (current_header->header.typeflag,
¤t_stat_info);
(*do_something) ();
static struct compression_suffix compression_suffixes[] = {
#define __CAT2__(a,b) a ## b
#define S(s,p) #s, sizeof (#s) - 1, __CAT2__(p,_PROGRAM)
+ { "tar", 3, NULL },
{ S(gz, GZIP) },
{ S(tgz, GZIP) },
{ S(taz, GZIP) },
{ S(lzo, LZOP) },
{ S(xz, XZ) },
{ S(txz, XZ) }, /* Slackware */
+ { NULL }
#undef S
#undef __CAT2__
};
-static int nsuffixes = sizeof (compression_suffixes) /
- sizeof (compression_suffixes[0]);
-
-static const char *
-find_compression_program (const char *name, const char *defprog)
+static struct compression_suffix const *
+find_compression_suffix (const char *name, size_t *base_len)
{
char *suf = strrchr (name, '.');
if (suf)
{
- int i;
size_t len;
-
+ struct compression_suffix *p;
+
suf++;
len = strlen (suf);
- for (i = 0; i < nsuffixes; i++)
+ for (p = compression_suffixes; p->suffix; p++)
{
- if (compression_suffixes[i].length == len
- && memcmp (compression_suffixes[i].suffix, suf, len) == 0)
- return compression_suffixes[i].program;
+ if (p->length == len && memcmp (p->suffix, suf, len) == 0)
+ {
+ if (*base_len)
+ *base_len = strlen (name) - len - 1;
+ return p;
+ }
}
}
+ return NULL;
+}
+
+static const char *
+find_compression_program (const char *name, const char *defprog)
+{
+ struct compression_suffix const *p = find_compression_suffix (name, NULL);
+ if (p)
+ return p->program;
return defprog;
}
if (program)
use_compress_program_option = program;
}
+
+char *
+strip_compression_suffix (const char *name)
+{
+ char *s = NULL;
+ size_t len;
+
+ if (find_compression_suffix (name, &len))
+ {
+ if (strncmp (name + len - 4, ".tar", 4) == 0)
+ len -= 4;
+ if (len == 0)
+ return NULL;
+ s = xmalloc (len + 1);
+ memcpy (s, name, len);
+ s[len] = 0;
+ }
+ return s;
+}
+
{"keep-directory-symlink", KEEP_DIRECTORY_SYMLINK_OPTION, 0, 0,
N_("preserve existing symlinks to directories when extracting"),
GRID+1 },
- {"one-top-level", ONE_TOP_LEVEL_OPTION, 0, 0,
+ {"one-top-level", ONE_TOP_LEVEL_OPTION, N_("DIR"), OPTION_ARG_OPTIONAL,
N_("create a subdirectory to avoid having loose files extracted"),
GRID+1 },
#undef GRID
case ONE_TOP_LEVEL_OPTION:
one_top_level_option = true;
+ one_top_level_dir = arg;
break;
case 'l':
multiv07.at\
multiv08.at\
old.at\
+ onetop01.at\
+ onetop02.at\
+ onetop03.at\
opcomp01.at\
opcomp02.at\
opcomp03.at\
--- /dev/null
+# Process this file with autom4te to create testsuite. -*- Autotest -*-
+#
+# Test suite for GNU tar.
+# Copyright 2014 Free Software Foundation, Inc.
+#
+# This file is part of GNU tar.
+#
+# GNU tar 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.
+#
+# GNU tar 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/>.
+#
+AT_SETUP([tar --one-top-level])
+AT_KEYWORDS([extract onetop onetop01])
+
+AT_TAR_CHECK([
+AT_SORT_PREREQ
+mkdir a
+genfile --file a/b
+genfile --file c
+tar cf a.tar a c
+mkdir out
+cd out
+tar --one-top-level -x -f ../a.tar
+find . | sort
+],
+[0],
+[.
+./a
+./a/b
+./a/c
+])
+
+AT_CLEANUP
--- /dev/null
+# Process this file with autom4te to create testsuite. -*- Autotest -*-
+#
+# Test suite for GNU tar.
+# Copyright 2014 Free Software Foundation, Inc.
+#
+# This file is part of GNU tar.
+#
+# GNU tar 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.
+#
+# GNU tar 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/>.
+#
+AT_SETUP([tar --one-top-level --show-transformed])
+AT_KEYWORDS([extract onetop onetop02])
+
+AT_TAR_CHECK([
+AT_SORT_PREREQ
+mkdir a
+genfile --file a/b
+genfile --file c
+tar cf a.tar a c
+mkdir out
+cd out
+tar --one-top-level --show-transformed -v -x -f ../a.tar
+find . | sort
+],
+[0],
+[a/
+a/b
+a/c
+.
+./a
+./a/b
+./a/c
+])
+
+AT_CLEANUP
--- /dev/null
+# Process this file with autom4te to create testsuite. -*- Autotest -*-
+#
+# Test suite for GNU tar.
+# Copyright 2014 Free Software Foundation, Inc.
+#
+# This file is part of GNU tar.
+#
+# GNU tar 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.
+#
+# GNU tar 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/>.
+#
+AT_SETUP([tar --one-top-level --transform])
+AT_KEYWORDS([extract onetop onetop02])
+
+AT_TAR_CHECK([
+AT_SORT_PREREQ
+mkdir a
+genfile --file a/b
+genfile --file c
+tar cf a.tar a c
+mkdir out
+cd out
+tar --one-top-level --transform 's/c/d/' -x -f ../a.tar
+find . | sort
+],
+[0],
+[.
+./a
+./a/b
+./a/d
+])
+
+AT_CLEANUP
m4_include([capabs_raw01.at])
+AT_BANNER([One top level])
+m4_include([onetop01.at])
+m4_include([onetop02.at])
+m4_include([onetop03.at])
+
AT_BANNER([Star tests])
m4_include([star/gtarfail.at])
m4_include([star/gtarfail2.at])