/* This file is part of GNU tar. Copyright (C) 2009 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, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include "common.h" #include struct deferred_unlink { struct deferred_unlink *next; /* Next unlink in the queue */ char *file_name; /* Absolute name of the file to unlink */ 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 */ }; /* The unlink queue */ static struct deferred_unlink *dunlink_head, *dunlink_tail; /* Number of entries in the queue */ static size_t dunlink_count; /* List of entries available for allocation */ 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 struct deferred_unlink * dunlink_alloc (void) { struct deferred_unlink *p; if (dunlink_avail) { p = dunlink_avail; dunlink_avail = p->next; p->next = NULL; } else p = xmalloc (sizeof (*p)); return p; } static void dunlink_reclaim (struct deferred_unlink *p) { free (p->file_name); p->next = dunlink_avail; dunlink_avail = p; } static void flush_deferred_unlinks (bool force) { struct deferred_unlink *p, *prev = NULL; for (p = dunlink_head; p; ) { struct deferred_unlink *next = p->next; if (force || records_written > p->records_written + deferred_unlink_delay) { if (p->is_dir) { if (unlinkat (chdir_fd, p->file_name, AT_REMOVEDIR) != 0) { switch (errno) { case ENOENT: /* 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 */ default: rmdir_error (p->file_name); } } } else { if (unlinkat (chdir_fd, p->file_name, 0) != 0 && errno != ENOENT) unlink_error (p->file_name); } dunlink_reclaim (p); dunlink_count--; p = next; if (prev) prev->next = p; else dunlink_head = p; } else { prev = p; p = next; } } if (!dunlink_head) dunlink_tail = NULL; } void finish_deferred_unlinks (void) { flush_deferred_unlinks (true); while (dunlink_avail) { struct deferred_unlink *next = dunlink_avail->next; free (dunlink_avail); dunlink_avail = next; } } void queue_deferred_unlink (const char *name, bool is_dir) { struct deferred_unlink *p; 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->is_dir = is_dir; p->records_written = records_written; if (dunlink_tail) dunlink_tail->next = p; else dunlink_head = p; dunlink_tail = p; dunlink_count++; }