Hello,

my Team and I found a behaviour of tar which I assume is a bug or a
possibility for an improvement. I tested with tar versions: 1.27
and 1.29.

Current Behaviour (1.27/1.29):
------------------------------
In case a directory in a deep directory is removed during
creation of an incremental tar file, tar exits with a
TAREXIT_FAILURE (==2).

This is the same issue as tested by test-case 'filerem01.at' of
the tar testsuite. However, the test-case tests the deletion of
a file and not of a directory.

Expected Behaviour:
-------------------
Tar should exit with TAREXIT_DIFFERS (==1).

Reason: I have use-cases where not only files, but also complete
        folders vanish during creation of the archive.

        To be able to distinguish these cases from other serious
        issues like actual I/O errors I would like to have the
        same behaviour as for vanishing files
        (i.e., exit-code == 1).



Test-cases:
===========
During my experiments to reproduce the issues I experienced, I
created two test-cases dirrem01.at and dirrem02.at based on the
test-cases filerem01/02.at:


dirrem01.at: Exit with TAREXIT_DIFFERS for the scenario
             described above
dirrem02.at: Still exit with TAREXIT_FAILURE if an explicitly
             named directory vanishes

Attached file: 0002-Test-handling-of-directories-removed-during-incremen.patch



A Trivial Patch Idea:
=====================
Based on these two test-cases I created a patch modifying
create_archive() to produce the behaviour I expected:

Attached file: 0001-Fix-handling-of-directories-removed-during-increment.patch (The patch is based on release_1_29 / 20b55f0679d314568ec21ae6db1ea635494e292b)

This was the first time I took a look into the gnu tar
source-code, therefore I try to provide a rather detailed
explanation on why I assume this patch could be a valid
modification.

If I missed anything I am more than happy to hear your comments.


Behaviour when create_archive(...) fails to open or stat a
directory:
 - Remark: '->' depicts a function call

 - Old Behaviour: open_diag()/stat_diag() is called.
   -------------------------------------------------
   open_diag()/stat_diag():
     if(!ignore_failed_read_option)
     -----------------------------
          -> open_error()/stat_error() -> call_arg_error()
             -> ERROR()<i> -> error()

           => <i>: exit_status = PAXEXIT_FAILURE (==2);
           => processing is continued

           (<i>: call to func ptr error_hook / func
            checkpoint_flush_actions not further discussed here)
     else
     ----
          -> open_warn()/stat_warn() -> call_arg_warn() -> WARN()
             -> error()

          => no modification of exit_status
          => processing is continued

 - New Behaviour: file_removed_diag() is called.
   ---------------------------------------------
   file_removed_diag():
     if(!top_level && errno == ENOENT))
     ----------------------------------
        -> set_exit_status(TAREXIT_DIFFERS) <ii>

        => <ii>: exit_status = TAREXIT_DIFFERS (if not already >=
           TAREXIT_DIFFERS)
        => processing is continued

     else
     ----
        -> open_diag()/stat_diag() [=> old behaviour]


=> Both, the patched and the non-patched code do not immediately
   terminate the execution, the difference is the exit-code.
   As far as I can see, this is the only difference (besides the
   modified text-output).

Table I: Exit-codes of patch / non-patched code
               |Case depicted  | Case depicted in dirrem01.at + |
               |in dirrem01.at |           --ignore-failed-read |
-----------------------------------------------------------------
Non-Patched  : |             2 |                              0 |
-----------------------------------------------------------------
Patched      : |             1 |                              1 |



Open issues I am aware of:
--------------------------
 - Wrong text output (File vs. Directory)
   Output of dirrem01.at: "dir/sub: File removed before we read
                           it"

   I assume a function dir_removed_diag should be added and
   used, similar to file_removed_diag.

 - Test-cases are only executed for gnu

Open questions from my side:
----------------------------
 - Are the modified locations sufficient? (i.e., are there other
   locations in the code which I may have missed; in particular code
   which will be triggered on subsequent incremental tar runs)

 - Assuming I did not miss anything and the only relevant
   difference between the patched / non-patched code is the
   modified exit-code:

   The patch leads to a scenario (dirrem01.at) where tar was
   returning TAREXIT_FAILURE and now returns TAREXIT_DIFFERS
   instead.
   The content of the resulting archive should be the same
   (see assumption).

   Is it save to assume the resulting archive is a valid archive
   (and can be unpacked)?

   An argument supporting the archive should be valid:
   The non-patched tar with option '--ignore-failed-read' leads
   to the same archive and exit-code 0.
   Therefore reporting exit-code 1 for the same archive seems
   not to be completely unreasonable.

 - Should the modified behaviour only be enabled by a command
   line option?
   (Reason: Preserve old behaviour if this report is considered
   to be an improvement and not to be a bug)





Regards and thanks for reading,
Jonas

---
Jonas Julino

1&1 Internet SE

Hauptsitz Montabaur, Amtsgericht Montabaur, HRB 24498
Vorstand: Christian Genis Bigatà Joseph, Robert Hoffmann, Hans-Henning Kettler, Uwe Lamnek
Aufsichtsratsvorsitzender: Michael Scheeren
>From 864821bc4c0be0f9d5320bc00530d53aa2d1d151 Mon Sep 17 00:00:00 2001
From: Jonas Julino <[email protected]>
Date: Mon, 10 Apr 2017 16:01:53 +0200
Subject: [PATCH 2/2] Test handling of directories removed during incremental
 tar run

---
 tests/Makefile.am  |  2 ++
 tests/dirrem01.at  | 76 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 tests/dirrem02.at  | 74 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 tests/testsuite.at |  4 +++
 4 files changed, 156 insertions(+)
 create mode 100644 tests/dirrem01.at
 create mode 100644 tests/dirrem02.at

diff --git a/tests/Makefile.am b/tests/Makefile.am
index 3e94276..1ae145c 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -108,6 +108,8 @@ TESTSUITE_AT = \
  extrac19.at\
  filerem01.at\
  filerem02.at\
+ dirrem01.at\
+ dirrem02.at\
  gzip.at\
  grow.at\
  incremental.at\
diff --git a/tests/dirrem01.at b/tests/dirrem01.at
new file mode 100644
index 0000000..d67d8e1
--- /dev/null
+++ b/tests/dirrem01.at
@@ -0,0 +1,76 @@
+# Process this file with autom4te to create testsuite. -*- Autotest -*-
+
+# Test suite for GNU tar.
+# Copyright 2009, 2013 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/>.
+
+# Description:
+#
+# When a directory in a deep directory disappeared during creation
+# of incremental dump, tar <= 1.29 exits with TAREXIT_FAILURE (2).
+#
+# In case of files tar does not return TAREXIT_FAILURE, but instead
+# it prints a warning and exits with TAREXIT_DIFFERS.
+#
+# This test checks whether this behaviour is mimicked for directories, too.
+
+# Remark: This file is based on 'filerem01.at', which is the test-case for
+# a vanishing file.
+
+AT_SETUP([dir removed as we read it (ca. 8 seconds)])
+AT_KEYWORDS([create incremental listed dirchange dirrem dirrem01])
+
+AT_TAR_CHECK([
+mkdir dir
+mkdir dir/sub
+genfile --file dir/file1
+genfile --file dir/sub/file2
+
+genfile --run --checkpoint=3 --exec='rmdir dir/sub' --checkpoint=3 --unlink dir/sub/file2 -- \
+       tar --blocking-factor=1 --checkpoint=1 --checkpoint-action='sleep=1' \
+       --checkpoint-action='echo' -c -f archive.tar \
+       --listed-incremental db -v dir >/dev/null
+],
+[1],
+[ignore],
+[tar: dir: Directory is new
+tar: dir/sub: Directory is new
+tar: dir/sub: File removed before we read it
+],[],[],[gnu])
+
+# Timing information:
+#
+# For -Hgnu the above command line takes about 8 seconds to execute and
+# produces:
+#
+# tar: dir: Directory is new
+# tar: dir/sub: Directory is new
+# dir/
+# tar: Write checkpoint 1
+# tar: Write checkpoint 2
+# dir/sub/
+# tar: Write checkpoint 3
+# tar: Write checkpoint 4
+# dir/file1
+# tar: Write checkpoint 5
+# dir/sub/file2
+# tar: Write checkpoint 6
+# tar: Write checkpoint 7
+# tar: Write checkpoint 8
+
+AT_CLEANUP
+
diff --git a/tests/dirrem02.at b/tests/dirrem02.at
new file mode 100644
index 0000000..b011725
--- /dev/null
+++ b/tests/dirrem02.at
@@ -0,0 +1,74 @@
+# Process this file with autom4te to create testsuite. -*- Autotest -*-
+
+# Test suite for GNU tar.
+# Copyright 2009, 2013 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/>.
+
+# Description:
+#
+# When an explicitley named directory disappears during creation
+# of incremental dump, tar should still exit with TAREXIT_FAILURE (2).
+#
+# For further details see dirrem01.at
+
+# Remark: This file is based on 'filerem01/02.at', which are test-cases for
+# vanishing files.
+
+AT_SETUP([explicitley named dir removed as we read it (ca. 8 seconds)])
+AT_KEYWORDS([create incremental listed dirchange dirrem dirrem02])
+
+AT_TAR_CHECK([
+mkdir dir
+mkdir dir/sub
+genfile --file dir/file1
+genfile --file dir/sub/file2
+
+genfile --run --checkpoint=3 --exec='rmdir dir/sub' --checkpoint=3 --unlink dir/sub/file2 -- \
+       tar --blocking-factor=1 --checkpoint=1 --checkpoint-action='sleep=1' \
+       --checkpoint-action='echo' -c -f archive.tar \
+       --listed-incremental db -v dir dir/sub >/dev/null
+],
+[2],
+[ignore],
+[tar: dir: Directory is new
+tar: dir/sub: Directory is new
+tar: dir/sub: Cannot open: No such file or directory
+tar: Exiting with failure status due to previous errors
+],[],[],[gnu])
+
+# Timing information:
+#
+# For -Hgnu the above command line takes about 8 seconds to execute and
+# produces:
+#
+# tar: dir: Directory is new
+# tar: dir/sub: Directory is new
+# dir/
+# tar: Write checkpoint 1
+# tar: Write checkpoint 2
+# dir/sub/
+# tar: Write checkpoint 3
+# tar: Write checkpoint 4
+# dir/file1
+# tar: Write checkpoint 5
+# dir/sub/file2
+# tar: Write checkpoint 6
+# tar: Write checkpoint 7
+# tar: Write checkpoint 8
+
+AT_CLEANUP
+
diff --git a/tests/testsuite.at b/tests/testsuite.at
index cf4b2fd..604b737 100644
--- a/tests/testsuite.at
+++ b/tests/testsuite.at
@@ -331,6 +331,10 @@ AT_BANNER([Files removed while archiving])
 m4_include([filerem01.at])
 m4_include([filerem02.at])
 
+AT_BANNER([Directories removed while archiving])
+m4_include([dirrem01.at])
+m4_include([dirrem02.at])
+
 AT_BANNER([Renames])
 m4_include([rename01.at])
 m4_include([rename02.at])
-- 
2.7.4

>From 09eb4b3272e9e50fd3053aac9ec36016b1f39f90 Mon Sep 17 00:00:00 2001
From: Jonas Julino <[email protected]>
Date: Mon, 10 Apr 2017 16:37:39 +0200
Subject: [PATCH 1/2] Fix handling of directories removed during incremental
 tar run

---
 src/create.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/create.c b/src/create.c
index 3a0f2dc..66c939f 100644
--- a/src/create.c
+++ b/src/create.c
@@ -1391,13 +1391,13 @@ create_archive (void)
 					   open_searchdir_flags);
 			  if (fd < 0)
 			    {
-			      open_diag (p->name);
+			      file_removed_diag(p->name, !p->parent, open_diag);
 			      break;
 			    }
 			  st.fd = fd;
 			  if (fstat (fd, &st.stat) != 0)
 			    {
-			      stat_diag (p->name);
+			      file_removed_diag(p->name, !p->parent, stat_diag);
 			      break;
 			    }
 			  st.orig_file_name = xstrdup (p->name);
-- 
2.7.4

Reply via email to