X-Git-Url: https://git.gag.com/?a=blobdiff_plain;f=src%2Funlink.c;h=6ae51ce1fc1e575cb5dc4c57da865cc42c2f5628;hb=HEAD;hp=2af6f9957b2887eea74b9fe0b394207d60937e32;hpb=3322ff6164a1e9dd3d1622c64a9b9b7c5f303ef6;p=debian%2Ftar diff --git a/src/unlink.c b/src/unlink.c index 2af6f995..6ae51ce1 100644 --- a/src/unlink.c +++ b/src/unlink.c @@ -1,19 +1,21 @@ -/* This file is part of GNU tar. - Copyright (C) 2009 Free Software Foundation, Inc. +/* Unlink files. - 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. + Copyright 2009, 2013-2014, 2016 Free Software Foundation, Inc. - 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. + This file is part of GNU tar. - 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. */ + 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 . */ #include #include "common.h" @@ -22,12 +24,18 @@ struct deferred_unlink { struct deferred_unlink *next; /* Next unlink in the queue */ - char *file_name; /* Absolute name of the file to unlink */ + int dir_idx; /* Directory index in wd */ + char *file_name; /* Name of the file to unlink, relative + to dir_idx */ bool is_dir; /* True if file_name is a directory */ off_t records_written; /* Number of records written when this 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; @@ -39,10 +47,10 @@ static struct deferred_unlink *dunlink_avail; /* Delay (number of records written) between adding entry to the list and its actual removal. */ -size_t deferred_unlink_delay = 0; +static size_t deferred_unlink_delay = 0; static struct deferred_unlink * -dunlink_alloc () +dunlink_alloc (void) { struct deferred_unlink *p; if (dunlink_avail) @@ -56,6 +64,24 @@ dunlink_alloc () 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) { @@ -68,40 +94,54 @@ static void 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; + if (force || records_written > p->records_written + deferred_unlink_delay) { + chdir_do (p->dir_idx); if (p->is_dir) { - if (rmdir (p->file_name) != 0) + const char *fname; + + if (p->dir_idx && IS_CWD (p)) + { + prev = p; + p = next; + continue; + } + else + fname = p->file_name; + + if (unlinkat (chdir_fd, fname, AT_REMOVEDIR) != 0) { switch (errno) { 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 (p->file_name); + rmdir_error (fname); } } } else { - if (unlink (p->file_name) != 0 && errno != ENOENT) + if (unlinkat (chdir_fd, p->file_name, 0) != 0 && errno != ENOENT) unlink_error (p->file_name); } dunlink_reclaim (p); @@ -116,16 +156,46 @@ flush_deferred_unlinks (bool force) { prev = p; p = next; - } + } } 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); } void -finish_deferred_unlinks () +finish_deferred_unlinks (void) { flush_deferred_unlinks (true); + while (dunlink_avail) { struct deferred_unlink *next = dunlink_avail->next; @@ -142,17 +212,26 @@ queue_deferred_unlink (const char *name, bool is_dir) if (dunlink_head && records_written > dunlink_head->records_written + deferred_unlink_delay) flush_deferred_unlinks (false); - + p = dunlink_alloc (); p->next = NULL; - p->file_name = normalize_filename (name); + p->dir_idx = chdir_current; + p->file_name = xstrdup (name); + normalize_filename_x (p->file_name); 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); }