/* Unlink files. Copyright 2009, 2013-2014 Free Software Foundation, Inc. This file is part of GNU tar. 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" #include struct deferred_unlink { struct deferred_unlink *next; /* Next unlink in the queue */ 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 */ }; /* 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. */ static 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; 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) { const char *fname; if (p->dir_idx && (p->file_name[0] == 0 || strcmp (p->file_name, ".") == 0)) { fname = tar_dirname (); chdir_do (p->dir_idx - 1); } else fname = p->file_name; if (unlinkat (chdir_fd, fname, 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 (fname); } } } 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; chdir_do (saved_chdir); } 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->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; else dunlink_head = p; dunlink_tail = p; dunlink_count++; }