Browse Source

Fix incremental archiving of renamed directories.

* src/incremen.c (struct directory): New member `next'.  Change
type of `name'.
(dirhead, dirtail): New statics.
(make_directory): Reflect changes to struct directory.
(free_directory, attach_directory): New functions.
(dirlist_replace_prefix): New function.
(note_directory): Use attach_directory, instead of make_directory,
(find_directory, find_directory_meta): Use free_directory.
(procdir): Replace directory prefixes in directory list to avoid
marking subdirectories as renamed after renaming their parent
directory.
(append_incremental_renames): Iterate over directory list, not
hash table, to preserve logical ordering of renames.
* tests/rename04.at, tests/rename05.at: New test cases.
* tests/Makefile.am, tests/testsuite.at: Add rename04.at and
rename05.at.
* tests/atlocal.in (decho): New function.
* tests/multiv06.at: Use decho instead of echo2.
* tests/incremental.at: Raise wait interval to 2 seconds.
Sergey Poznyakoff 16 years ago
parent
commit
dbbffde583
9 changed files with 276 additions and 40 deletions
  1. 9 1
      ChangeLog
  2. 88 30
      src/incremen.c
  3. 2 0
      tests/Makefile.am
  4. 4 0
      tests/atlocal.in
  5. 4 1
      tests/incremental.at
  6. 3 8
      tests/multiv06.at
  7. 83 0
      tests/rename04.at
  8. 81 0
      tests/rename05.at
  9. 2 0
      tests/testsuite.at

+ 9 - 1
ChangeLog

@@ -1,8 +1,16 @@
+2008-07-31  Sergey Poznyakoff  <[email protected]>
+
+	* src/incremen.c (struct directory): New member `next'.  Change
+	type of `name'.
+	(dirhead, dirtail): New statics.
+	(make_directory): Reflect changes to struct directory.
+	(free_directory, attach_directory): New functions.
+	
+
 2008-07-24  Sergey Poznyakoff  <[email protected]>
 
 	* src/tar.c (decode_options): Do not allow volume length less
 	than record size.
-
 	* src/buffer.c (_gnu_flush_write): Compensate for the effect
 	of eventual flush_archive occurring in the middle of buffer
 	move.

+ 88 - 30
src/incremen.c

@@ -60,6 +60,7 @@ struct dumpdir                 /* Dump directory listing */
 /* Directory attributes.  */
 struct directory
   {
+    struct directory *next;
     struct timespec mtime;      /* Modification time */
     dev_t device_number;	/* device number for directory */
     ino_t inode_number;		/* inode number for directory */
@@ -72,7 +73,7 @@ struct directory
 				   the original directory structure */
     const char *tagfile;        /* Tag file, if the directory falls under
 				   exclusion_tag_under */
-    char name[1];		/* file name of directory */
+    char *name;	     	        /* file name of directory */
   };
 
 struct dumpdir *
@@ -196,6 +197,7 @@ dumpdir_size (const char *p)
 }
 
 
+static struct directory *dirhead, *dirtail;
 static Hash_table *directory_table;
 static Hash_table *directory_meta_table;
 
@@ -247,18 +249,67 @@ static struct directory *
 make_directory (const char *name)
 {
   size_t namelen = strlen (name);
-  size_t size = offsetof (struct directory, name) + namelen + 1;
-  struct directory *directory = xmalloc (size);
+  struct directory *directory = xmalloc (sizeof (*directory));
+  directory->next = NULL;
   directory->dump = directory->idump = NULL;
   directory->orig = NULL;
   directory->flags = false;
-  strcpy (directory->name, name);
-  if (namelen && ISSLASH (directory->name[namelen - 1]))
-    directory->name[namelen - 1] = 0;
+  if (namelen && ISSLASH (name[namelen - 1]))
+    namelen--;
+  directory->name = xmalloc (namelen + 1);
+  memcpy (directory->name, name, namelen);
+  directory->name[namelen] = 0;
   directory->tagfile = NULL;
   return directory;
 }
 
+static void
+free_directory (struct directory *dir)
+{
+  free (dir->name);
+  free (dir);
+}
+
+static struct directory *
+attach_directory (const char *name)
+{
+  struct directory *dir = make_directory (name);
+  if (dirtail)
+    dirtail->next = dir;
+  else
+    dirhead = dir;
+  dirtail = dir;
+}
+		 
+
+static void
+replace_prefix (char **pname, const char *samp, size_t slen,
+		const char *repl, size_t rlen)
+{
+  char *name = *pname;
+  size_t nlen = strlen (name);
+  if (nlen > slen && memcmp (name, samp, slen) == 0 && ISSLASH (name[slen]))
+    {
+      if (rlen > slen)
+	{
+	  name = xrealloc (name, nlen - slen + rlen + 1);
+	  *pname = name;
+	}
+      memmove (name + rlen, name + slen, nlen - slen + 1);
+      memcpy (name, repl, rlen);
+    }
+}
+
+void
+dirlist_replace_prefix (const char *pref, const char *repl)
+{
+  struct directory *dp;
+  size_t pref_len = strlen (pref);
+  size_t repl_len = strlen (repl);
+  for (dp = dirhead; dp; dp = dp->next)
+    replace_prefix (&dp->name, pref, pref_len, repl, repl_len);
+}
+
 /* Create and link a new directory entry for directory NAME, having a
    device number DEV and an inode number INO, with NFS indicating
    whether it is an NFS device and FOUND indicating whether we have
@@ -268,7 +319,7 @@ note_directory (char const *name, struct timespec mtime,
 		dev_t dev, ino_t ino, bool nfs, bool found,
 		const char *contents)
 {
-  struct directory *directory = make_directory (name);
+  struct directory *directory = attach_directory (name);
 
   directory->mtime = mtime;
   directory->device_number = dev;
@@ -311,7 +362,7 @@ find_directory (const char *name)
     {
       struct directory *dir = make_directory (name);
       struct directory *ret = hash_lookup (directory_table, dir);
-      free (dir);
+      free_directory (dir);
       return ret;
     }
 }
@@ -330,7 +381,7 @@ find_directory_meta (dev_t dev, ino_t ino)
       dir->device_number = dev;
       dir->inode_number = ino;
       ret = hash_lookup (directory_meta_table, dir);
-      free (dir);
+      free_directory (dir);
       return ret;
     }
 }
@@ -386,12 +437,16 @@ procdir (char *name_buffer, struct stat *stat_data,
 						     stat_data->st_ino);
 	  if (d)
 	    {
-	      if (verbose_option)
-		WARN ((0, 0, _("%s: Directory has been renamed from %s"),
-		       quotearg_colon (name_buffer),
-		       quote_n (1, d->name)));
-	      directory->orig = d;
-	      DIR_SET_FLAG (directory, DIRF_RENAMED);
+	      if (strcmp (d->name, name_buffer))
+		{
+		  if (verbose_option)
+		    WARN ((0, 0, _("%s: Directory has been renamed from %s"),
+			   quotearg_colon (name_buffer),
+			   quote_n (1, d->name)));
+		  directory->orig = d;
+		  DIR_SET_FLAG (directory, DIRF_RENAMED);
+		  dirlist_replace_prefix (d->name, name_buffer);
+		}
 	      directory->children = CHANGED_CHILDREN;
 	    }
 	  else
@@ -426,12 +481,16 @@ procdir (char *name_buffer, struct stat *stat_data,
 
       if (d)
 	{
-	  if (verbose)
-	    WARN ((0, 0, _("%s: Directory has been renamed from %s"),
-		   quotearg_colon (name_buffer),
-		   quote_n (1, d->name)));
-	  directory->orig = d;
-	  DIR_SET_FLAG (directory, DIRF_RENAMED);
+	  if (strcmp (d->name, name_buffer))
+	    {
+	      if (verbose)
+		WARN ((0, 0, _("%s: Directory has been renamed from %s"),
+		       quotearg_colon (name_buffer),
+		       quote_n (1, d->name)));
+	      directory->orig = d;
+	      DIR_SET_FLAG (directory, DIRF_RENAMED);
+	      dirlist_replace_prefix (d->name, name_buffer);
+	    }
 	  directory->children = CHANGED_CHILDREN;
 	}
       else
@@ -701,12 +760,9 @@ obstack_code_rename (struct obstack *stk, char *from, char *to)
   obstack_grow (stk, s, strlen (s) + 1);
 }
 
-static bool
-rename_handler (void *data, void *proc_data)
+static void
+store_rename (struct directory *dir, struct obstack *stk)
 {
-  struct directory *dir = data;
-  struct obstack *stk = proc_data;
-
   if (DIR_IS_RENAMED (dir))
     {
       struct directory *prev, *p;
@@ -745,7 +801,6 @@ rename_handler (void *data, void *proc_data)
 	  obstack_code_rename (stk, "", prev->name);
 	}
     }
-  return true;
 }
 
 const char *
@@ -753,8 +808,9 @@ append_incremental_renames (const char *dump)
 {
   struct obstack stk;
   size_t size;
-
-  if (directory_table == NULL)
+  struct directory *dp;
+  
+  if (dirhead == NULL)
     return dump;
 
   obstack_init (&stk);
@@ -766,7 +822,9 @@ append_incremental_renames (const char *dump)
   else
     size = 0;
 
-  hash_do_for_each (directory_table, rename_handler, &stk);
+  for (dp = dirhead; dp; dp = dp->next)
+    store_rename (dp, &stk);
+
   if (obstack_object_size (&stk) != size)
     {
       obstack_1grow (&stk, 0);

+ 2 - 0
tests/Makefile.am

@@ -98,6 +98,8 @@ TESTSUITE_AT = \
  rename01.at\
  rename02.at\
  rename03.at\
+ rename04.at\
+ rename05.at\
  same-order01.at\
  same-order02.at\
  shortfile.at\

+ 4 - 0
tests/atlocal.in

@@ -30,4 +30,8 @@ tarball_prereq() {
   echo "$2  $3/$1" | md5sum --status --check - >/dev/null 2>&1
 }
 
+decho() {
+  echo $*
+  echo >&2 $*
+}
 

+ 4 - 1
tests/incremental.at

@@ -44,7 +44,10 @@ sleep 1
 tar cf archive --listed=list structure
 tar cfv archive --listed=list structure
 echo separator
-sleep 1
+# ReiserFS often offsets the timestamps of newly created files
+# 1 second to the past.  Try to compensate for it, until a better
+# solution is found.
+sleep 2
 echo y >structure/file
 tar cfv archive --listed=list structure
 ],

+ 3 - 8
tests/multiv06.at

@@ -27,18 +27,13 @@
 AT_SETUP([Multivolumes with L=record_size])
 AT_KEYWORDS([multivolume multiv multiv06])
 
-m4_define([echo2],[
-echo $*
-echo >&2 $*
-])
-
 AT_TAR_CHECK([
 exec <&-
-echo2("Creating file")
+decho Creating file
 genfile --length 20139 --file file
-echo2("Creating archive") 
+decho Creating archive
 tar -c -M -L10 -b20 -farc.1 -farc.2 -farc.3 file
-echo2("Testing archive")
+decho Testing archive
 tar -t -M -farc.1 -farc.2 -farc.3],
 [0],
 [Creating file

+ 83 - 0
tests/rename04.at

@@ -0,0 +1,83 @@
+# Process this file with autom4te to create testsuite. -*- Autotest -*-
+
+# Test suite for GNU tar.
+# Copyright (C) 2008 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, 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, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301, USA.
+
+# Description: Up to version 1.20, when storing a record for renamed
+# directory in an incremental archive, tar incorrectly flagged all its
+# subdirectories as renamed, which led to problems at archive extraction.
+# References: <[email protected]>
+# Reported by: Enric Hernandez <[email protected]>
+
+AT_SETUP([renamed directory containing subdirectories])
+AT_KEYWORDS([incremental rename04 rename])
+
+AT_TAR_CHECK([
+AT_SORT_PREREQ
+
+decho Creating directory structure
+mkdir directory
+mkdir directory/subdir
+genfile --file=directory/file
+
+decho Creating initial archive
+tar -cf archive.1 -g db.1 directory
+
+decho Renaming
+mv directory dir
+
+decho Creating incremental archive
+cp db.1 db.2
+tar -cf archive.2 -g db.2 dir
+
+mv dir orig
+
+decho First restore
+tar -xf archive.1 -g db.1
+find directory | sort
+
+decho Second restore
+tar -xf archive.2 -g db.2
+find dir | sort
+],
+[0],
+[Creating directory structure
+Creating initial archive
+Renaming
+Creating incremental archive
+First restore
+directory
+directory/file
+directory/subdir
+Second restore
+dir
+dir/subdir
+],
+[Creating directory structure
+Creating initial archive
+Renaming
+Creating incremental archive
+First restore
+Second restore
+],[],[],[gnu, oldgnu, posix])
+
+AT_CLEANUP
+
+# End of rename04.at
+
+

+ 81 - 0
tests/rename05.at

@@ -0,0 +1,81 @@
+# Process this file with autom4te to create testsuite. -*- Autotest -*-
+
+# Test suite for GNU tar.
+# Copyright (C) 2008 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, 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, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301, USA.
+
+# Description:  A continuation of rename04.at, that checks additionally if
+# renamed subdirectories are restored correctly.
+
+AT_SETUP([renamed subdirectories])
+AT_KEYWORDS([incremental rename05 rename])
+
+AT_TAR_CHECK([
+AT_SORT_PREREQ
+
+decho Creating directory structure
+mkdir directory
+mkdir directory/subdir
+genfile --file=directory/file
+
+decho Creating initial archive
+tar -cf archive.1 -g db.1 directory
+
+decho Renaming
+mv directory/subdir directory/subdir.0
+mv directory dir
+
+decho Creating incremental archive
+cp db.1 db.2
+tar -cf archive.2 -g db.2 dir
+
+mv dir orig
+
+decho First restore
+tar -xf archive.1 -g db.1
+find directory | sort
+
+decho Second restore
+tar -xf archive.2 -g db.2
+find dir | sort
+],
+[0],
+[Creating directory structure
+Creating initial archive
+Renaming
+Creating incremental archive
+First restore
+directory
+directory/file
+directory/subdir
+Second restore
+dir
+dir/subdir.0
+],
+[Creating directory structure
+Creating initial archive
+Renaming
+Creating incremental archive
+First restore
+Second restore
+],[],[],[gnu, oldgnu, posix])
+
+AT_CLEANUP
+
+# End of rename05.at
+
+

+ 2 - 0
tests/testsuite.at

@@ -138,6 +138,8 @@ m4_include([incr04.at])
 m4_include([rename01.at])
 m4_include([rename02.at])
 m4_include([rename03.at])
+m4_include([rename04.at])
+m4_include([rename05.at])
 m4_include([chtype.at])
 
 m4_include([ignfail.at])