123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239 |
- /* Unlink files.
- Copyright 2009-2023 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 <http://www.gnu.org/licenses/>. */
- #include <system.h>
- #include "common.h"
- #include <quotearg.h>
- 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 */
- };
- #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;
- /* 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_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);
- 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 && 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 */
- #if defined ENOTEMPTY && ENOTEMPTY != EEXIST
- case ENOTEMPTY:
- #endif
- /* 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);
- }
- }
- }
- 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;
- 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 (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 (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_insert (dunlink_tail, p);
- }
|