summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Eggert <eggert@cs.ucla.edu>2010-10-15 22:26:14 -0700
committerPaul Eggert <eggert@cs.ucla.edu>2010-10-15 22:26:43 -0700
commit9447e799ab5faa6e8be43de4000e1061e0879242 (patch)
tree573841c910e94608bd14c1aa79a1f78ba8d9454a
parent502abd93bd8bfba15677aceb221c4530d257bbab (diff)
downloadtar-9447e799ab5faa6e8be43de4000e1061e0879242.tar.gz
tar: use more-accurate diagnostic when intermediate mkdir fails
Without this change, if tar tried to extract a file A/B/C, noticed that A/B didn't exist, attempted to mkdir A/B, and the mkdir failed, it did not diagnose the mkdir failure, but simply reported the failure to open A/B/C. This sometimes led to confusion because it wasn't clear what tar was trying to do, in particular that tar tried to mkdir A/B. With this patch, tar issues two diagnostics in this case: one for A/B and the other for A/B/C. Problem reported by Hauke Laging in <http://lists.gnu.org/archive/html/bug-tar/2010-10/msg00020.html>. * gnulib.modules: Remove faccessat. * src/extract.c (make_directories): New arg INTERDIR_MADE. Diagnose mkdir failure. Return 0 on success, nonzero on failure, as opposed to nonzero iff some directory was created. All callers changed. Simplify the code when mkdir fails, by checking whether the desired file exists unless errno==EEXIST: this is more robust. * tests/extrac15.at: New test, to check this. * tests/Makefile.am (TESTSUITE_AT): Add it. * tests/testsuite.at: Include it.
-rw-r--r--gnulib.modules1
-rw-r--r--src/extract.c58
-rw-r--r--tests/Makefile.am1
-rw-r--r--tests/extrac15.at44
-rw-r--r--tests/testsuite.at1
5 files changed, 76 insertions, 29 deletions
diff --git a/gnulib.modules b/gnulib.modules
index fbf70782..49eab656 100644
--- a/gnulib.modules
+++ b/gnulib.modules
@@ -12,7 +12,6 @@ dirname
error
exclude
exitfail
-faccessat
fdopendir
fdutimensat
fileblocks
diff --git a/src/extract.c b/src/extract.c
index 0a1a0329..0d23d4ab 100644
--- a/src/extract.c
+++ b/src/extract.c
@@ -478,20 +478,20 @@ repair_delayed_set_stat (char const *dir,
/* After a file/link/directory creation has failed, see if
it's because some required directory was not present, and if so,
- create all required directories. Return non-zero if a directory
- was created. */
+ create all required directories. Return zero if all the required
+ directories were created, nonzero (issuing a diagnostic) otherwise.
+ Set *INTERDIR_MADE if at least one directory was created. */
static int
-make_directories (char *file_name)
+make_directories (char *file_name, bool *interdir_made)
{
char *cursor0 = file_name + FILE_SYSTEM_PREFIX_LEN (file_name);
char *cursor; /* points into the file name */
- int did_something = 0; /* did we do anything yet? */
- int status;
for (cursor = cursor0; *cursor; cursor++)
{
mode_t mode;
mode_t desired_mode;
+ int status;
if (! ISSLASH (*cursor))
continue;
@@ -524,28 +524,32 @@ make_directories (char *file_name)
desired_mode, AT_SYMLINK_NOFOLLOW);
print_for_mkdir (file_name, cursor - file_name, desired_mode);
- did_something = 1;
-
- *cursor = '/';
- continue;
+ *interdir_made = true;
+ }
+ else if (errno == EEXIST)
+ status = 0;
+ else
+ {
+ /* Check whether the desired file exists. Even when the
+ file exists, mkdir can fail with some errno value E other
+ than EEXIST, so long as E describes an error condition
+ that also applies. */
+ int e = errno;
+ struct stat st;
+ status = fstatat (chdir_fd, file_name, &st, 0);
+ if (status)
+ {
+ errno = e;
+ mkdir_error (file_name);
+ }
}
*cursor = '/';
-
- if (errno == EEXIST)
- continue; /* Directory already exists. */
- else if ((errno == ENOSYS /* Automounted dirs on Solaris return
- this. Reported by Warren Hyde
- <Warren.Hyde@motorola.com> */
- || ERRNO_IS_EACCES) /* Turbo C mkdir gives a funny errno. */
- && faccessat (chdir_fd, file_name, W_OK, AT_EACCESS) == 0)
- continue;
-
- /* Some other error in the mkdir. We return to the caller. */
- break;
+ if (status)
+ return status;
}
- return did_something; /* tell them to retry if we made one */
+ return 0;
}
/* Return true if FILE_NAME (with status *STP, if STP) is not a
@@ -644,11 +648,8 @@ maybe_recoverable (char *file_name, bool regular, bool *interdir_made)
case ENOENT:
/* Attempt creating missing intermediate directories. */
- if (make_directories (file_name))
- {
- *interdir_made = true;
- return RECOVER_OK;
- }
+ if (make_directories (file_name, interdir_made) == 0 && *interdir_made)
+ return RECOVER_OK;
break;
default:
@@ -1544,11 +1545,12 @@ rename_directory (char *src, char *dst)
if (renameat (chdir_fd, src, chdir_fd, dst) != 0)
{
int e = errno;
+ bool interdir_made;
switch (e)
{
case ENOENT:
- if (make_directories (dst))
+ if (make_directories (dst, &interdir_made) == 0)
{
if (renameat (chdir_fd, src, chdir_fd, dst) == 0)
return true;
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 4bf3ff0c..d29563a8 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -81,6 +81,7 @@ TESTSUITE_AT = \
extrac12.at\
extrac13.at\
extrac14.at\
+ extrac15.at\
filerem01.at\
filerem02.at\
gzip.at\
diff --git a/tests/extrac15.at b/tests/extrac15.at
new file mode 100644
index 00000000..bf8d1cf1
--- /dev/null
+++ b/tests/extrac15.at
@@ -0,0 +1,44 @@
+# Process this file with autom4te to create testsuite. -*- Autotest -*-
+
+# Test suite for GNU tar.
+# Copyright (C) 2010 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, see <http://www.gnu.org/licenses/>.
+
+# written by Paul Eggert
+
+# Check diagnostic of 'tar -x a/b/c' when b cannot be created.
+
+AT_SETUP([extract parent mkdir failure])
+AT_KEYWORDS([extract extrac15])
+
+AT_TAR_CHECK([
+AT_UNPRIVILEGED_PREREQ
+
+mkdir src src/a src/a/b dest dest/a
+touch src/a/b/c
+chmod a-w dest/a
+
+tar -cf archive.tar -C src a/b/c &&
+if tar -xf archive.tar -C dest a/b/c
+then (exit 1)
+else (exit 0)
+fi
+],
+[0],[],[tar: a/b: Cannot mkdir: Permission denied
+tar: a/b/c: Cannot open: No such file or directory
+tar: Exiting with failure status due to previous errors
+],[],[],[gnu])
+
+AT_CLEANUP
diff --git a/tests/testsuite.at b/tests/testsuite.at
index c047bd2f..c3868926 100644
--- a/tests/testsuite.at
+++ b/tests/testsuite.at
@@ -153,6 +153,7 @@ m4_include([extrac11.at])
m4_include([extrac12.at])
m4_include([extrac13.at])
m4_include([extrac14.at])
+m4_include([extrac15.at])
m4_include([label01.at])
m4_include([label02.at])