X-Git-Url: https://git.gag.com/?a=blobdiff_plain;f=src%2Funlink.c;h=6ae51ce1fc1e575cb5dc4c57da865cc42c2f5628;hb=d30babc23b4f25be970ada2e63a50220a3672281;hp=7f86cc5844a16f3aeecd1f96a69a34a2cf80de5d;hpb=4aa85f09e755fc827cd5ab6225f20c83cd42245d;p=debian%2Ftar diff --git a/src/unlink.c b/src/unlink.c index 7f86cc58..6ae51ce1 100644 --- a/src/unlink.c +++ b/src/unlink.c @@ -1,6 +1,6 @@ /* Unlink files. - Copyright 2009, 2013-2014 Free Software Foundation, Inc. + Copyright 2009, 2013-2014, 2016 Free Software Foundation, Inc. This file is part of GNU tar. @@ -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; @@ -60,6 +64,24 @@ dunlink_alloc (void) return p; } +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) { @@ -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; @@ -103,16 +124,16 @@ flush_deferred_unlinks (bool force) case ENOENT: /* nothing to worry about */ break; + case EEXIST: + /* OpenSolaris >=10 sets EEXIST instead of ENOTEMPTY + if trying to remove a non-empty directory */ 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 +160,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 +195,7 @@ void finish_deferred_unlinks (void) { flush_deferred_unlinks (true); + while (dunlink_avail) { struct deferred_unlink *next = dunlink_avail->next; @@ -171,10 +221,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); }