From dbbffde5835102b0cb1361c9f8dc716a38060d0d Mon Sep 17 00:00:00 2001 From: Sergey Poznyakoff Date: Thu, 31 Jul 2008 07:12:50 +0000 Subject: [PATCH] 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. --- ChangeLog | 10 ++++- src/incremen.c | 118 ++++++++++++++++++++++++++++++++++++++------------- tests/Makefile.am | 2 + tests/atlocal.in | 4 ++ tests/incremental.at | 5 ++- tests/multiv06.at | 11 ++--- tests/rename04.at | 83 ++++++++++++++++++++++++++++++++++++ tests/rename05.at | 81 +++++++++++++++++++++++++++++++++++ tests/testsuite.at | 2 + 9 files changed, 276 insertions(+), 40 deletions(-) create mode 100644 tests/rename04.at create mode 100644 tests/rename05.at diff --git a/ChangeLog b/ChangeLog index b2e94524..21c972f0 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,8 +1,16 @@ +2008-07-31 Sergey Poznyakoff + + * 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 * 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. diff --git a/src/incremen.c b/src/incremen.c index 372061dd..d23e45c7 100644 --- a/src/incremen.c +++ b/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); diff --git a/tests/Makefile.am b/tests/Makefile.am index 263f6eef..cad0dfed 100644 --- a/tests/Makefile.am +++ b/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\ diff --git a/tests/atlocal.in b/tests/atlocal.in index 380cac01..5eaaab68 100644 --- a/tests/atlocal.in +++ b/tests/atlocal.in @@ -30,4 +30,8 @@ tarball_prereq() { echo "$2 $3/$1" | md5sum --status --check - >/dev/null 2>&1 } +decho() { + echo $* + echo >&2 $* +} diff --git a/tests/incremental.at b/tests/incremental.at index bab0beba..5873d83a 100644 --- a/tests/incremental.at +++ b/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 ], diff --git a/tests/multiv06.at b/tests/multiv06.at index 4df9cd71..96652995 100644 --- a/tests/multiv06.at +++ b/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 diff --git a/tests/rename04.at b/tests/rename04.at new file mode 100644 index 00000000..3e9eb1a2 --- /dev/null +++ b/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: <00a401c8ecc0$56b7ef30$6a17a8c0@inti.com> +# Reported by: Enric Hernandez + +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 + + diff --git a/tests/rename05.at b/tests/rename05.at new file mode 100644 index 00000000..3ad93a67 --- /dev/null +++ b/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 + + diff --git a/tests/testsuite.at b/tests/testsuite.at index 11f108bd..2fa53925 100644 --- a/tests/testsuite.at +++ b/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]) -- 2.11.4.GIT