There has been recent discussion around whether GNU tar should have an option to deal with archives that do not follow the practice of having everything in one directory. As an alternative to more involved patches that have been proposed, this simply adds a new error to help the user not see as many unpleasant surprises. Various workarounds could then be used on an individual basis.
Signed-off-by: Connor Behan <connor.be...@gmail.com> --- doc/tar.texi | 7 +++++++ src/common.h | 3 +++ src/extract.c | 23 +++++++++++++++++++++++ src/tar.c | 7 +++++++ 4 files changed, 40 insertions(+) diff --git a/doc/tar.texi b/doc/tar.texi index 2661174..ef76cfe 100644 --- a/doc/tar.texi +++ b/doc/tar.texi @@ -3270,6 +3270,13 @@ the archive creation operations it instructs @command{tar} to list the member names stored in the archive, as opposed to the actual file names. @xref{listing member and file names}. +@opsummary{single-top-level} +@item --single-top-level + +Tells @command{tar} to extract only one name from the archive into its +parent directory or the one specified with @option{-C}. A fatal error is +thrown if a second one is found. + @opsummary{skip-old-files} @item --skip-old-files diff --git a/src/common.h b/src/common.h index 21df8c1..0cf7da3 100644 --- a/src/common.h +++ b/src/common.h @@ -270,6 +270,9 @@ GLOBAL size_t strip_name_components; GLOBAL bool show_omitted_dirs_option; +/* Refuse to extract more than one name that does not contain a slash */ +GLOBAL bool single_top_level_option; + GLOBAL bool sparse_option; GLOBAL unsigned tar_sparse_major; GLOBAL unsigned tar_sparse_minor; diff --git a/src/extract.c b/src/extract.c index 319aaa8..8f77a88 100644 --- a/src/extract.c +++ b/src/extract.c @@ -1559,6 +1559,25 @@ prepare_to_extract (char const *file_name, int typeflag, tar_extractor_t *fun) return 1; } +/* Throws an error if passed file name is the second top-level name. */ +void +top_level_check (char *file_name) +{ + static bool has_top_level = false; + int i; + + for (i = 0; i < strlen (file_name); i++) + if (ISSLASH (file_name[i])) break; + + if (i == strlen (file_name)) + { + if (has_top_level) + FATAL_ERROR ((0, 0, _("More than one top-level name found, exiting"))); + + has_top_level = true; + } +} + /* Extract a file from the archive. */ void extract_archive (void) @@ -1604,6 +1623,10 @@ extract_archive (void) return; } + /* Ensure that we are not extracting more than one top-level file. */ + if (single_top_level_option) + top_level_check (current_stat_info.file_name); + /* Extract the archive entry according to its type. */ /* KLUDGE */ typeflag = sparse_member_p (¤t_stat_info) ? diff --git a/src/tar.c b/src/tar.c index c3c2459..1f4c9da 100644 --- a/src/tar.c +++ b/src/tar.c @@ -338,6 +338,7 @@ enum SHOW_DEFAULTS_OPTION, SHOW_OMITTED_DIRS_OPTION, SHOW_TRANSFORMED_NAMES_OPTION, + SINGLE_TOP_LEVEL_OPTION, SKIP_OLD_FILES_OPTION, SPARSE_VERSION_OPTION, STRIP_COMPONENTS_OPTION, @@ -484,6 +485,8 @@ static struct argp_option options[] = { {"overwrite-dir", OVERWRITE_DIR_OPTION, 0, 0, N_("overwrite metadata of existing directories when extracting (default)"), GRID+1 }, + {"single-top-level", SINGLE_TOP_LEVEL_OPTION, 0, 0, + N_("allow at most one top-level name when extracting"), GRID+1 }, #undef GRID #define GRID 40 @@ -1548,6 +1551,10 @@ parse_opt (int key, char *arg, struct argp_state *state) sparse_option = true; break; + case SINGLE_TOP_LEVEL_OPTION: + single_top_level_option = true; + break; + case SKIP_OLD_FILES_OPTION: old_files_option = SKIP_OLD_FILES; break; -- 1.8.3.4