On Sunday 15 of November 2015 17:13:43 Sergey Poznyakoff wrote:
> Pavel Raiskup <prais...@redhat.com> ha escrit:
> 
> > issues like that (bug in library call) should be fixed within
> > gnulib;
> 
> In general - yes, I agree.  But in this particular case it was exactly
> because it fixes remfiles08b,remfiles09b that I pushed it to the repo.

I rebased the patches (mostly because of copyright year bump) and
attaching again.

Pavel
>From 6d3f62a3935810bb36dd4a1d7fc887e84d5b4d71 Mon Sep 17 00:00:00 2001
From: Pavel Raiskup <prais...@redhat.com>
Date: Fri, 11 Jul 2014 07:36:58 +0200
Subject: [PATCH 1/3] tar: avoid recursion in directory traversal

This commit avoids unnecessary recursion from dump_file() call.
Directory "stack" is created on heap space instead to avoid
segfaults in case of rarely deep directory structures.  Note that
the recursion still exists in case of incremental archives.

Relevant tread:
http://www.mail-archive.com/bug-tar@gnu.org/msg04542.html

* src/create.c (dump_dir0): Rename to dump_dir as the wrapper
itself caused that additional calls to free() had to exist.
(dump_dir): Return bool.  Use the tour_t context and call
tour_plan_dir/tour_plan_file to emulate recursion from now.
(open_failure_recover): Rather dup() (sensitively) the directory
descriptor for fdopendir() purposes which allows us to save a lot
of memory in case of deep dir structures.
(create_archive): Use dump_file_incr for incremental dumps and
dump_member for usual dumps instead of dump_file in general.
(dump_file0): Reuse tour_t context.  Let the tar_stat_close on
"tour" mechanism.
(dump_file): Rename to dump_member.
(dump_member): The member name parameter is enough from now.  Use
the tour_t & tour_* handlers.
* update.c (update_archive): Call dump_member from now.
* src/tour.c: New file.
* src/common.h: Export needed symbols from xlist.c.
* gnulib.modules: Use array-list and xlist.
* src/Makefile.am: Compile also tour.c file.
* NEWS: Document.
---
 NEWS            |   4 +
 gnulib.modules  |   2 +
 src/Makefile.am |   1 +
 src/common.h    |  31 +++++++-
 src/create.c    | 148 +++++++++++++++++------------------
 src/tour.c      | 234 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/unlink.c    |   4 +-
 src/update.c    |   2 +-
 8 files changed, 344 insertions(+), 82 deletions(-)
 create mode 100644 src/tour.c

diff --git a/NEWS b/NEWS
index 52e3f57..4941fe5 100644
--- a/NEWS
+++ b/NEWS
@@ -130,6 +130,10 @@ speed up archivation.
 
 * Tar refuses to read input from and write output to a tty device.
 
+* Tar avoids calling recursive rutines for directory traversal (for usual
+  scenarios) to save stack-space and thus avoid segfaults for rarely deep
+  archived directories.
+
 * Manpages
 
 This release includes official tar(1) and rmt(8) manpages.
diff --git a/gnulib.modules b/gnulib.modules
index fe5ab73..f2ce146 100644
--- a/gnulib.modules
+++ b/gnulib.modules
@@ -23,6 +23,7 @@ areadlinkat-with-size
 argmatch
 argp
 argp-version-etc
+array-list
 backupfile
 closeout
 configmake
@@ -102,5 +103,6 @@ version-etc-fsf
 xalloc
 xalloc-die
 xgetcwd
+xlist
 xstrtoumax
 xvasprintf
diff --git a/src/Makefile.am b/src/Makefile.am
index dc05d5c..2ef5637 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -40,6 +40,7 @@ tar_SOURCES = \
  suffix.c\
  system.c\
  tar.c\
+ tour.c\
  transform.c\
  unlink.c\
  update.c\
diff --git a/src/common.h b/src/common.h
index 09e42e1..4537e5a 100644
--- a/src/common.h
+++ b/src/common.h
@@ -492,7 +492,8 @@ char *get_directory_entries (struct tar_stat_info *st);
 
 void create_archive (void);
 void pad_archive (off_t size_left);
-void dump_file (struct tar_stat_info *parent, char const *name,
+void dump_member (char const *member);
+void dump_file_incr (struct tar_stat_info *parent, char const *name,
 		char const *fullname);
 union block *start_header (struct tar_stat_info *st);
 void finish_header (struct tar_stat_info *st, union block *header,
@@ -963,5 +964,33 @@ int owner_map_translate (uid_t uid, uid_t *new_uid, char const **new_name);
 void group_map_read (char const *file);
 int group_map_translate (gid_t gid, gid_t *new_gid, char const **new_name);
 
+/* Module tour.c */
+
+/* hide TOUR definition in tour.c */
+typedef struct tour *tour_t;
+
+/* One tour_node_t represents single directory level in FS hierarchy,
+   i.e. list of files.  At one moment we keep in memory only one path
+   representation. */
+typedef struct
+{
+  struct tar_stat_info *parent;         /* used to fill child's stat info */
+  char                 *items;          /* output from readdir */
+
+  /* per-directory volatile data to avoid reallocation for each file in
+   * directory */
+  const char           *item;     /* processed filename (ptr into ITEMS) */
+  struct tar_stat_info  st;       /* stat of processed file */
+  char                 *namebuf;  /* buffer for constructing full name */
+  size_t                buflen;   /* length of NAMEBUF */
+} tour_node_t;
+
+tour_t tour_init (const char *, struct tar_stat_info *);
+bool tour_next (tour_t, const char **, const char **);
+tour_node_t *tour_current (tour_t);
+void tour_plan_dir (tour_t, char *);
+void tour_plan_file (tour_t, const char *);
+bool tour_has_child (tour_t t);
+void tour_free (tour_t t);
 
 _GL_INLINE_HEADER_END
diff --git a/src/create.c b/src/create.c
index 58a530d..b2e6001 100644
--- a/src/create.c
+++ b/src/create.c
@@ -1097,12 +1097,15 @@ dump_regular_file (int fd, struct tar_stat_info *st)
 }
 
 
-/* Copy info from the directory identified by ST into the archive.
-   DIRECTORY contains the directory's entries.  */
-
-static void
-dump_dir0 (struct tar_stat_info *st, char const *directory)
+/* Dump currently precessed directory in T.  Return true if successful,
+   false (emitting diagnostics) otherwise.  Get ST's entries, recurse
+   through its subfiles, and clean up file descriptors afterwards.
+   */
+static bool
+dump_dir (tour_t t)
 {
+  tour_node_t *n = tour_current (t);
+  struct tar_stat_info *st = &n->st;
   bool top_level = ! st->parent;
   const char *tag_file_name;
   union block *blk = NULL;
@@ -1112,7 +1115,7 @@ dump_dir0 (struct tar_stat_info *st, char const *directory)
 
   blk = start_header (st);
   if (!blk)
-    return;
+    return false;
 
   info_attach_exclist (st);
 
@@ -1167,11 +1170,11 @@ dump_dir0 (struct tar_stat_info *st, char const *directory)
 	      set_next_block_after (blk + (bufsize - 1) / BLOCKSIZE);
 	    }
 	}
-      return;
+      return true;
     }
 
   if (!recursion_option)
-    return;
+    return true;
 
   if (one_file_system_option
       && !top_level
@@ -1185,9 +1188,6 @@ dump_dir0 (struct tar_stat_info *st, char const *directory)
     }
   else
     {
-      char *name_buf;
-      size_t name_size;
-
       switch (check_exclusion_tags (st, &tag_file_name))
 	{
 	case exclusion_tag_all:
@@ -1196,40 +1196,20 @@ dump_dir0 (struct tar_stat_info *st, char const *directory)
 
 	case exclusion_tag_none:
 	  {
-	    char const *entry;
-	    size_t entry_len;
-	    size_t name_len;
-
-	    name_buf = xstrdup (st->orig_file_name);
-	    name_size = name_len = strlen (name_buf);
-
-	    /* Now output all the files in the directory.  */
-	    for (entry = directory; (entry_len = strlen (entry)) != 0;
-		 entry += entry_len + 1)
-	      {
-		if (name_size < name_len + entry_len)
-		  {
-		    name_size = name_len + entry_len;
-		    name_buf = xrealloc (name_buf, name_size + 1);
-		  }
-		strcpy (name_buf + name_len, entry);
-		if (!excluded_name (name_buf, st))
-		  dump_file (st, entry, name_buf);
-	      }
-
-	    free (name_buf);
+            char *directory = get_directory_entries (st);
+            if (! directory)
+              {
+                savedir_diag (st->orig_file_name);
+                return false;
+              }
+            tour_plan_dir (t, directory);
 	  }
 	  break;
 
 	case exclusion_tag_contents:
 	  exclusion_tag_warning (st->orig_file_name, tag_file_name,
 				 _("contents not dumped"));
-	  name_size = strlen (st->orig_file_name) + strlen (tag_file_name) + 1;
-	  name_buf = xmalloc (name_size);
-	  strcpy (name_buf, st->orig_file_name);
-	  strcat (name_buf, tag_file_name);
-	  dump_file (st, tag_file_name, name_buf);
-	  free (name_buf);
+          tour_plan_file (t, tag_file_name);
 	  break;
 
 	case exclusion_tag_under:
@@ -1238,6 +1218,7 @@ dump_dir0 (struct tar_stat_info *st, char const *directory)
 	  break;
 	}
     }
+  return true;
 }
 
 /* Ensure exactly one trailing slash.  */
@@ -1288,30 +1269,19 @@ open_failure_recover (struct tar_stat_info const *dir)
 char *
 get_directory_entries (struct tar_stat_info *st)
 {
-  while (! (st->dirstream = fdopendir (st->fd)))
+  int newfd;
+  while ((newfd = dup (st->fd)) == -1)
     if (! open_failure_recover (st))
       return 0;
-  return streamsavedir (st->dirstream, savedir_sort_order);
-}
 
-/* Dump the directory ST.  Return true if successful, false (emitting
-   diagnostics) otherwise.  Get ST's entries, recurse through its
-   subdirectories, and clean up file descriptors afterwards.  */
-static bool
-dump_dir (struct tar_stat_info *st)
-{
-  char *directory = get_directory_entries (st);
-  if (! directory)
-    {
-      savedir_diag (st->orig_file_name);
-      return false;
-    }
-
-  dump_dir0 (st, directory);
+  while (! (st->dirstream = fdopendir (newfd)))
+    if (! open_failure_recover (st))
+      return 0;
 
-  restore_parent_fd (st);
-  free (directory);
-  return true;
+  char *x = streamsavedir (st->dirstream, savedir_sort_order);
+  closedir (st->dirstream);
+  st->dirstream = 0;
+  return x;
 }
 
 
@@ -1343,7 +1313,7 @@ create_archive (void)
 
       while ((p = name_from_list ()) != NULL)
 	if (!excluded_name (p->name, NULL))
-	  dump_file (0, p->name, p->name);
+	  dump_file_incr (0, p->name, p->name);
 
       blank_name_list ();
       while ((p = name_from_list ()) != NULL)
@@ -1392,7 +1362,7 @@ create_archive (void)
 			  buffer = xrealloc (buffer, buffer_size);
  			}
 		      strcpy (buffer + plen, q + 1);
-		      dump_file (&st, q + 1, buffer);
+		      dump_file_incr (&st, q + 1, buffer);
 		    }
 		  q += qlen + 1;
 		}
@@ -1405,7 +1375,7 @@ create_archive (void)
       const char *name;
       while ((name = name_next (1)) != NULL)
 	if (!excluded_name (name, NULL))
-	  dump_file (0, name, name);
+	  dump_member (name);
     }
 
   write_eot ();
@@ -1630,9 +1600,13 @@ restore_parent_fd (struct tar_stat_info const *st)
 /* FIXME: One should make sure that for *every* path leading to setting
    exit_status to failure, a clear diagnostic has been issued.  */
 
+#include <assert.h>
 static void
-dump_file0 (struct tar_stat_info *st, char const *name, char const *p)
+dump_file0 (tour_t t, char const *name, char const *p)
 {
+  tour_node_t *n = tour_current (t);
+  assert (n);
+  struct tar_stat_info *st = &n->st;
   union block *header;
   char type;
   off_t original_size;
@@ -1640,7 +1614,7 @@ dump_file0 (struct tar_stat_info *st, char const *name, char const *p)
   off_t block_ordinal = -1;
   int fd = 0;
   bool is_dir;
-  struct tar_stat_info const *parent = st->parent;
+  struct tar_stat_info const *parent = st->parent = n->parent;
   bool top_level = ! parent;
   int parentfd = top_level ? chdir_fd : parent->fd;
   void (*diag) (char const *) = 0;
@@ -1751,7 +1725,7 @@ dump_file0 (struct tar_stat_info *st, char const *name, char const *p)
 	      return;
 	    }
 
-	  ok = dump_dir (st);
+	  ok = dump_dir (t);
 
 	  fd = st->fd;
 	  parentfd = top_level ? chdir_fd : parent->fd;
@@ -1829,7 +1803,6 @@ dump_file0 (struct tar_stat_info *st, char const *name, char const *p)
 	    utime_error (p);
 	}
 
-      ok &= tar_stat_close (st);
       if (ok && remove_files_option)
 	queue_deferred_unlink (p, is_dir);
 
@@ -1936,20 +1909,37 @@ dump_file0 (struct tar_stat_info *st, char const *name, char const *p)
     queue_deferred_unlink (p, false);
 }
 
-/* Dump a file, recursively.  PARENT describes the file's parent
-   directory, NAME is the file's name relative to PARENT, and FULLNAME
-   its full name, possibly relative to the working directory.  NAME
-   may contain slashes at the top level of invocation.  */
-
+/* Dump a file, recursively.  The local TRINFO keeps gradually enlarging stack
+   of files to be processed in case of directories are present in processed path
+   starting from MEMBER. */
 void
-dump_file (struct tar_stat_info *parent, char const *name,
-	   char const *fullname)
+dump_member (char const *member)
 {
-  struct tar_stat_info st;
-  tar_stat_init (&st);
-  st.parent = parent;
-  dump_file0 (&st, name, fullname);
-  if (parent && listed_incremental_option)
+  const char *fullname;
+  const char *name;
+
+  tour_t t = tour_init (member, 0);
+  while (tour_next (t, &name, &fullname))
+    {
+      tour_node_t *n = tour_current (t);
+      if (n->parent && excluded_name (fullname, n->parent))
+        continue;
+
+      dump_file0 (t, name, fullname);
+    }
+  tour_free (t);
+}
+
+void dump_file_incr (struct tar_stat_info *parent, char const *name,
+                     const char *fullname)
+{
+  /* one-shot traversal;  we are in incremental mode so no sub-folders are going
+     to be added to tour_t directory stack */
+  tour_t t = tour_init (name, parent);
+
+  dump_file0 (t, name, fullname);
+  if (parent)
     update_parent_directory (parent);
-  tar_stat_destroy (&st);
+
+  tour_free (t);
 }
diff --git a/src/tour.c b/src/tour.c
new file mode 100644
index 0000000..a53e09d
--- /dev/null
+++ b/src/tour.c
@@ -0,0 +1,234 @@
+/* Create a tar archive.
+
+   Copyright 2015 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/>.
+
+   Routines making the directory traversal without function recursion.  This
+   avoids segfaults due to small stack space for rarely deep directory
+   hierarchies.
+
+   Written by Pavel Raiskup <prais...@redhat.com>, on 2014-07-15.  */
+
+
+#include <system.h>
+
+#include "common.h"
+
+#include <assert.h>
+#include <gl_xlist.h>
+#include <gl_array_list.h>
+
+struct tour
+{
+  gl_list_t list;
+  gl_list_node_t current;
+};
+
+
+static tour_node_t *
+tour_node_new (void)
+{
+  tour_node_t *node = xzalloc (sizeof (tour_node_t));
+  tar_stat_init (&node->st);
+  return node;
+}
+
+tour_t
+tour_init (const char *initial_name, struct tar_stat_info * parent)
+{
+  size_t len = strlen (initial_name);
+  tour_node_t *node;
+  tour_t t;
+
+  node = tour_node_new ();
+  node->items = xmalloc (len + 2);
+  node->items[len + 1] = 0;     /* double 0 */
+  strcpy (node->items, initial_name);
+  node->parent = parent;
+
+  /* current item is the first one */
+  node->item = node->items;
+
+  t = xzalloc (sizeof (struct tour));
+  t->list = gl_list_create_empty (GL_ARRAY_LIST, NULL, NULL, NULL, 1);
+
+  t->current = gl_list_add_last (t->list, node);
+  return t;
+}
+
+void
+tour_plan_dir (tour_t t, char *names)
+{
+  tour_node_t *node, *curr;
+
+  curr = tour_current (t);
+
+  if (strlen (names) == 0)
+    {
+      /* no child planned */
+      free (names);
+      return;
+    }
+
+  node = tour_node_new ();
+  node->items = names;
+  node->parent = &curr->st;
+  node->item = node->items;
+
+  gl_list_add_last (t->list, node);
+}
+
+void
+tour_plan_file (tour_t t, const char *name)
+{
+  size_t nlen = strlen (name);
+  char *dirlist = xmalloc (nlen + 2);
+  dirlist[nlen + 1] = 0;
+  strcpy (dirlist, name);
+
+  tour_plan_dir (t, dirlist);
+}
+
+tour_node_t *
+tour_current (tour_t t)
+{
+  if (!t->current)
+    return NULL;
+  return (tour_node_t *) gl_list_node_value (t->list, t->current);
+}
+
+bool
+tour_has_child (tour_t t)
+{
+  return !!gl_list_next_node (t->list, t->current);
+}
+
+static tour_node_t *
+tour_next_node (tour_t t)
+{
+  gl_list_node_t next = gl_list_next_node (t->list, t->current);
+  if (!next)
+    return NULL;
+
+  t->current = next;
+  return (tour_node_t *) gl_list_node_value (t->list, next);
+}
+
+static void
+tour_destroy_node (tour_node_t * n)
+{
+  tar_stat_destroy (&n->st);
+  free (n->namebuf);
+  free (n->items);
+  free (n);
+}
+
+static tour_node_t *
+tour_prev_node (tour_t t)
+{
+  tour_node_t *data = tour_current (t);
+
+  /* we are going to drop current node -> make sure parent
+     fd is opened */
+  if (data->parent)
+    restore_parent_fd (data->parent);
+
+  gl_list_node_t n = gl_list_previous_node (t->list, t->current);;
+  gl_list_remove_node (t->list, t->current);
+  t->current = n;
+
+  data->st.parent = data->parent;
+
+  tour_destroy_node (data);
+
+  return tour_current (t);
+}
+
+/* Obtain the next NAME and FULLNAME (full path) of file in tour_t to bue dumped
+   (in correct order). */
+bool
+tour_next (tour_t t, const char **name, const char **fullname)
+{
+  size_t parent_nlen = 0, nlen = 0, len = 0;
+  tour_node_t *curr, *next;
+
+  curr = tour_current (t);
+
+  /* go to the next node when exists (go down to subfolder) */
+  if ((next = tour_next_node (t)))
+    curr = next;
+
+  /* when ended up the current directory, try to go up in directory */
+  while (!curr->item[0])
+    {
+      if ((curr = tour_prev_node (t)) == NULL)
+        /* last member-item has been processed */
+        return false;
+    }
+
+  /* now curr points to correct node/item */
+
+  /* re-init ST, TODO: here is good place to save some malloc/free calls? */
+  tar_stat_destroy (&curr->st);
+  curr->st.parent = curr->parent;
+
+  nlen = strlen (curr->item);
+  if (curr->parent)
+    parent_nlen = strlen (curr->parent->orig_file_name);
+
+  len = nlen + parent_nlen + 1;
+
+  if (!curr->namebuf)
+    {
+      /* first item of particular tour_node_t is handled */
+      curr->namebuf = xmalloc (len);
+      if (curr->parent)
+        strcpy (curr->namebuf, curr->parent->orig_file_name);
+      curr->buflen = len;
+    }
+  else if (curr->buflen < len)
+    {
+      curr->namebuf = xrealloc (curr->namebuf, len);
+      curr->buflen = len;
+    }
+
+  strcpy (curr->namebuf + parent_nlen, curr->item);
+  *name = curr->item;
+  if (fullname)
+    *fullname = curr->namebuf;
+
+  /* move the pointer for successive call tour () call */
+  curr->item += nlen + 1;
+
+  return true;
+}
+
+void
+tour_free (tour_t t)
+{
+  const void *data;
+
+  gl_list_iterator_t it = gl_list_iterator (t->list);
+  while (gl_list_iterator_next (&it, &data, NULL))
+    {
+      tour_node_t *n = (tour_node_t *) data;
+      tour_destroy_node (n);
+    }
+
+  gl_list_free (t->list);
+  free (t);
+}
diff --git a/src/unlink.c b/src/unlink.c
index 3e50757..c6d5bd2 100644
--- a/src/unlink.c
+++ b/src/unlink.c
@@ -46,7 +46,9 @@ static size_t dunlink_count;
 static struct deferred_unlink *dunlink_avail;
 
 /* Delay (number of records written) between adding entry to the
-   list and its actual removal. */
+   list and its actual removal.
+   TODO: remove? (unused currently)
+   */
 static size_t deferred_unlink_delay = 0;
 
 static struct deferred_unlink *
diff --git a/src/update.c b/src/update.c
index ad7a6bf..45f2ea7 100644
--- a/src/update.c
+++ b/src/update.c
@@ -223,7 +223,7 @@ update_archive (void)
 	if (subcommand_option == CAT_SUBCOMMAND)
 	  append_file (file_name);
 	else
-	  dump_file (0, file_name, file_name);
+	  dump_member (file_name);
       }
   }
 
-- 
2.5.0

>From ff907da04d2374678d2c835f8e9fa9d0dbc4764d Mon Sep 17 00:00:00 2001
From: Pavel Raiskup <prais...@redhat.com>
Date: Sun, 15 Nov 2015 12:54:19 +0100
Subject: [PATCH 3/3] incremen: don't indefinitely recurse into symlink loop

This complements previous commit.

* src/common.h (nl_dir_loop_point): New prototype.
* src/incremen.c (nl_dir_loop_point): New function.
* src/names.c (add_hierarchy_to_namelist): Don't recurse down to
the directory if the directory is already in namelist.  Use
nl_dir_loop_point for detection.
* tests/deref02.at: New testcase.
* tests/testsuite.at: Mention new testcase.
* tests/Makefile.am (TESTSUITE_AT): Likewise.
---
 src/common.h       |  1 +
 src/incremen.c     | 19 +++++++++++++++++++
 src/names.c        |  8 ++++++++
 tests/Makefile.am  |  1 +
 tests/deref02.at   | 40 ++++++++++++++++++++++++++++++++++++++++
 tests/testsuite.at |  1 +
 6 files changed, 70 insertions(+)
 create mode 100644 tests/deref02.at

diff --git a/src/common.h b/src/common.h
index 4537e5a..3a0ab57 100644
--- a/src/common.h
+++ b/src/common.h
@@ -559,6 +559,7 @@ void update_parent_directory (struct tar_stat_info *st);
 size_t dumpdir_size (const char *p);
 bool is_dumpdir (struct tar_stat_info *stat_info);
 void clear_directory_table (void);
+bool nl_dir_loop_point (struct name *name);
 
 /* Module list.c.  */
 
diff --git a/src/incremen.c b/src/incremen.c
index 2cbd16f..526f6fd 100644
--- a/src/incremen.c
+++ b/src/incremen.c
@@ -1758,6 +1758,25 @@ try_purge_directory (char const *directory_name)
   return true;
 }
 
+bool
+nl_dir_loop_point (struct name *name)
+{
+  const struct name *ptr = name;
+
+  if (!dereference_option)
+    return false;
+
+  while (ptr->parent)
+    {
+      ptr = ptr->parent;
+      if (ptr->directory->device_number == name->directory->device_number
+          && ptr->directory->inode_number == name->directory->inode_number)
+        return true;
+    }
+
+  return false;
+}
+
 void
 purge_directory (char const *directory_name)
 {
diff --git a/src/names.c b/src/names.c
index b7f7f49..597b8dd 100644
--- a/src/names.c
+++ b/src/names.c
@@ -1438,6 +1438,14 @@ add_hierarchy_to_namelist (struct tar_stat_info *st, struct name *name)
   const char *buffer;
 
   name->directory = scan_directory (st);
+
+  if (nl_dir_loop_point (name))
+    {
+      WARN ((0, 0, _("%s: stopping recursion due to directory loop"),
+             name->name));
+      return;
+    }
+
   buffer = directory_contents (name->directory);
   if (buffer)
     {
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 2759ad7..304166e 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -70,6 +70,7 @@ TESTSUITE_AT = \
  delete04.at\
  delete05.at\
  deref01.at\
+ deref02.at\
  exclude.at\
  exclude01.at\
  exclude02.at\
diff --git a/tests/deref02.at b/tests/deref02.at
new file mode 100644
index 0000000..0c20c08
--- /dev/null
+++ b/tests/deref02.at
@@ -0,0 +1,40 @@
+# Process this file with autom4te to create testsuite. -*- Autotest -*-
+#
+# Test suite for GNU tar.
+# Copyright 2015 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/>.
+#
+# Test description:
+# Symlink loop on directory with --dereference and --incremental.
+
+AT_SETUP([dereference: symlink loop and --incremental])
+AT_KEYWORDS([create dereference deref02 incremental])
+
+AT_TAR_CHECK([
+mkdir dir
+ln -s ../dir dir/dir || AT_SKIP_TEST
+tar --incremental -chf test.tar dir && tar -tf test.tar
+],
+[0],
+[dir/
+dir/dir/
+],
+[tar: dir/dir: stopping recursion due to directory loop
+],
+[],[],[gnu, oldgnu, posix])
+
+AT_CLEANUP
diff --git a/tests/testsuite.at b/tests/testsuite.at
index 8192d24..6ba1b26 100644
--- a/tests/testsuite.at
+++ b/tests/testsuite.at
@@ -233,6 +233,7 @@ m4_include([shortrec.at])
 m4_include([iotty.at])
 m4_include([numeric.at])
 m4_include([deref01.at])
+m4_include([deref02.at])
 
 AT_BANNER([The --same-order option])
 m4_include([same-order01.at])
-- 
2.5.0

>From e9bf910cd76baa834952c98437ba08c308e9b8f7 Mon Sep 17 00:00:00 2001
From: Pavel Raiskup <prais...@redhat.com>
Date: Wed, 9 Jul 2014 14:04:17 +0200
Subject: [PATCH 2/3] create: do not segfault with --dereference

Fix SIGSEGV during archiving of directory symbolic-link loop.
Prior to version 1.26, tar failed with message "Cannot stat: Too
many levels of symbolic links" but this limit does not "save" us
anymore since the s/open/openat/ conversion.

Original bugreport:
https://bugzilla.redhat.com/show_bug.cgi?id=1115890

* src/create.c (dir_loop_point): New function detecting loops in
directory path when --dereference is specified.
(dump_dir): Don't recurse down when dir_loop_point alerts.
* tests/deref01.at: New testcase.
* tests/Makefile.am: Adjust for new testcase.
* tests/testsuite.at: Likewise.
* NEWS: Document.
---
 NEWS               |  5 +++++
 src/create.c       | 31 ++++++++++++++++++++++++++++++-
 tests/Makefile.am  |  1 +
 tests/deref01.at   | 43 +++++++++++++++++++++++++++++++++++++++++++
 tests/testsuite.at |  1 +
 5 files changed, 80 insertions(+), 1 deletion(-)
 create mode 100644 tests/deref01.at

diff --git a/NEWS b/NEWS
index 4941fe5..17f266c 100644
--- a/NEWS
+++ b/NEWS
@@ -134,6 +134,11 @@ speed up archivation.
   scenarios) to save stack-space and thus avoid segfaults for rarely deep
   archived directories.
 
+* Bug fixes
+
+Fix segfault during archiving of directory symbolic-link loop with
+--dereference option.
+
 * Manpages
 
 This release includes official tar(1) and rmt(8) manpages.
diff --git a/src/create.c b/src/create.c
index b2e6001..ee4511f 100644
--- a/src/create.c
+++ b/src/create.c
@@ -1096,6 +1096,25 @@ dump_regular_file (int fd, struct tar_stat_info *st)
   return dump_status_ok;
 }
 
+static bool
+dir_loop_point (const struct tar_stat_info* st)
+{
+  const struct tar_stat_info *ptr = st;
+
+  if (!dereference_option)
+    return false;
+
+  while (ptr->parent)
+    {
+      ptr = ptr->parent;
+      if (ptr->stat.st_dev == st->stat.st_dev
+          && ptr->stat.st_ino == st->stat.st_ino)
+        return true;
+    }
+
+  return false;
+}
+
 
 /* Dump currently precessed directory in T.  Return true if successful,
    false (emitting diagnostics) otherwise.  Get ST's entries, recurse
@@ -1196,7 +1215,17 @@ dump_dir (tour_t t)
 
 	case exclusion_tag_none:
 	  {
-            char *directory = get_directory_entries (st);
+            char *directory;
+
+
+            if (dir_loop_point (st))
+              {
+                WARN ((0, 0, _("%s: stopping recursion due to directory loop"),
+                       st->orig_file_name));
+                break;
+              }
+
+            directory = get_directory_entries (st);
             if (! directory)
               {
                 savedir_diag (st->orig_file_name);
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 0772fab..2759ad7 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -69,6 +69,7 @@ TESTSUITE_AT = \
  delete03.at\
  delete04.at\
  delete05.at\
+ deref01.at\
  exclude.at\
  exclude01.at\
  exclude02.at\
diff --git a/tests/deref01.at b/tests/deref01.at
new file mode 100644
index 0000000..3acc39a
--- /dev/null
+++ b/tests/deref01.at
@@ -0,0 +1,43 @@
+# 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/>.
+#
+# Test description:
+# Symlink loop on directory with --dereference option caused tar to end in
+# infinite recursion ending on SIGSEGV.
+#
+# Original bugreport:
+# https://bugzilla.redhat.com/show_bug.cgi?id=1115890
+
+AT_SETUP([dereference: symlink loop])
+AT_KEYWORDS([create dereference deref01])
+
+AT_TAR_CHECK([
+mkdir dir
+ln -s ../dir dir/dir || AT_SKIP_TEST
+tar -chf test.tar dir && tar -tf test.tar
+],
+[0],
+[dir/
+dir/dir/
+],
+[tar: dir/dir/: stopping recursion due to directory loop
+])
+
+AT_CLEANUP
diff --git a/tests/testsuite.at b/tests/testsuite.at
index 954d48c..8192d24 100644
--- a/tests/testsuite.at
+++ b/tests/testsuite.at
@@ -232,6 +232,7 @@ m4_include([recurs02.at])
 m4_include([shortrec.at])
 m4_include([iotty.at])
 m4_include([numeric.at])
+m4_include([deref01.at])
 
 AT_BANNER([The --same-order option])
 m4_include([same-order01.at])
-- 
2.5.0

Attachment: testsuite.log.xz
Description: application/xz

Reply via email to