Gianfranco Costamagna <costamagnagianfra...@yahoo.it> ha escrit: > exodar:~$ gcc test.c && ./a.out > fstatat: No such file or directory > unlinkat: No such file or directory
Thanks, that confirms the nature of the bug. Please try the attached patch. Regards, Sergey
>From e6fcc73efa7463fe20c30a20603d3a3f6abfd4e5 Mon Sep 17 00:00:00 2001 From: Sergey Poznyakoff <g...@gnu.org> Date: Wed, 11 Nov 2015 13:01:45 +0200 Subject: [PATCH] Work around unlinkat bug on FreeBSD and GNU/Hurd * src/unlink.c (dunlink_insert): New function. (flush_deferred_unlinks): Skip cwds and nonempty directories at the first pass. If force is requested, run a second pass removing them. (queue_deferred_unlink): Make sure current working directory entries are sorted in descending order by the value of dir_idx. This makes sure they will be removed in right order, which works around unlinkat bug on FreeBSD and GNU/Hurd. * tests/remfiles08b.at: Remove expected failure. * tests/remfiles09b.at: Likewise. --- src/unlink.c | 94 +++++++++++++++++++++++++++++++++++++++++----------- tests/remfiles08b.at | 2 -- tests/remfiles09b.at | 2 -- 3 files changed, 74 insertions(+), 24 deletions(-) diff --git a/src/unlink.c b/src/unlink.c index 7f86cc5..509daf3 100644 --- a/src/unlink.c +++ b/src/unlink.c @@ -32,6 +32,10 @@ struct deferred_unlink entry got added to the queue */ }; +#define IS_CWD(p) \ + ((p)->is_dir \ + && ((p)->file_name[0] == 0 || strcmp ((p)->file_name, ".") == 0)) + /* The unlink queue */ static struct deferred_unlink *dunlink_head, *dunlink_tail; @@ -61,6 +65,24 @@ dunlink_alloc (void) } static void +dunlink_insert (struct deferred_unlink *anchor, struct deferred_unlink *p) +{ + if (anchor) + { + p->next = anchor->next; + anchor->next = p; + } + else + { + p->next = dunlink_head; + dunlink_head = p; + } + if (!p->next) + dunlink_tail = p; + dunlink_count++; +} + +static void dunlink_reclaim (struct deferred_unlink *p) { free (p->file_name); @@ -73,7 +95,7 @@ flush_deferred_unlinks (bool force) { struct deferred_unlink *p, *prev = NULL; int saved_chdir = chdir_current; - + for (p = dunlink_head; p; ) { struct deferred_unlink *next = p->next; @@ -86,12 +108,11 @@ flush_deferred_unlinks (bool force) { const char *fname; - if (p->dir_idx - && (p->file_name[0] == 0 - || strcmp (p->file_name, ".") == 0)) + if (p->dir_idx && IS_CWD (p)) { - fname = tar_dirname (); - chdir_do (p->dir_idx - 1); + prev = p; + p = next; + continue; } else fname = p->file_name; @@ -104,15 +125,12 @@ flush_deferred_unlinks (bool force) /* nothing to worry about */ break; case ENOTEMPTY: - if (!force) - { - /* Keep the record in list, in the hope we'll - be able to remove it later */ - prev = p; - p = next; - continue; - } - /* fall through */ + /* Keep the record in list, in the hope we'll + be able to remove it later */ + prev = p; + p = next; + continue; + default: rmdir_error (fname); } @@ -139,6 +157,34 @@ flush_deferred_unlinks (bool force) } if (!dunlink_head) dunlink_tail = NULL; + else if (force) + { + for (p = dunlink_head; p; ) + { + struct deferred_unlink *next = p->next; + const char *fname; + + chdir_do (p->dir_idx); + if (p->dir_idx && IS_CWD (p)) + { + fname = tar_dirname (); + chdir_do (p->dir_idx - 1); + } + else + fname = p->file_name; + + if (unlinkat (chdir_fd, fname, AT_REMOVEDIR) != 0) + { + if (errno != ENOENT) + rmdir_error (fname); + } + dunlink_reclaim (p); + dunlink_count--; + p = next; + } + dunlink_head = dunlink_tail = NULL; + } + chdir_do (saved_chdir); } @@ -146,6 +192,7 @@ void finish_deferred_unlinks (void) { flush_deferred_unlinks (true); + while (dunlink_avail) { struct deferred_unlink *next = dunlink_avail->next; @@ -171,10 +218,17 @@ queue_deferred_unlink (const char *name, bool is_dir) p->is_dir = is_dir; p->records_written = records_written; - if (dunlink_tail) - dunlink_tail->next = p; + if (IS_CWD (p)) + { + struct deferred_unlink *q, *prev; + for (q = dunlink_head, prev = NULL; q; prev = q, q = q->next) + if (IS_CWD (q) && q->dir_idx < p->dir_idx) + break; + if (q) + dunlink_insert (prev, p); + else + dunlink_insert (dunlink_tail, p); + } else - dunlink_head = p; - dunlink_tail = p; - dunlink_count++; + dunlink_insert (dunlink_tail, p); } diff --git a/tests/remfiles08b.at b/tests/remfiles08b.at index 13beaf4..5b3dd2c 100644 --- a/tests/remfiles08b.at +++ b/tests/remfiles08b.at @@ -31,8 +31,6 @@ AT_SETUP([remove-files deleting two subdirs in -c/incr. mode]) AT_KEYWORDS([create incremental remove-files remfiles08 remfiles08b]) -AT_XFAIL_IF(true) # we expect to fail in tar 1.27 - AT_TAR_CHECK([ mkdir foo mkdir bar diff --git a/tests/remfiles09b.at b/tests/remfiles09b.at index 45b8440..31597df 100644 --- a/tests/remfiles09b.at +++ b/tests/remfiles09b.at @@ -29,8 +29,6 @@ AT_SETUP([remove-files on full directory in -c/incr. mode]) AT_KEYWORDS([create incremental remove-files remfiles09 remfiles09b]) -AT_XFAIL_IF(true) # we expect to fail in tar 1.27 - AT_TAR_CHECK([ mkdir foo echo foo/file > foo/file -- 1.8.4