mirror of
https://git.savannah.gnu.org/git/tar.git
synced 2026-04-26 03:20:40 +00:00
Although this might help (or hurt) performance, the main motivation is to make it easier in future commits to prevent tarballs from escaping the extraction directory. * src/common.h: (BADFD): New constant. (struct fdbase): New type. * src/create.c (dump_file0): Use parent->fd instead of caching it into a local, as the latter approach is now awkward. * src/extract.c (extract_link): Don’t save errno unless needed. * src/misc.c (safer_rmdir): New arg F. All callers changed. (maybe_backup_file): Construct full after_backup_name, now that find_backup_file_name no longer does that for us. (chdir_fd): Now static not extern, as other modules now use fdbase. (fdbase_cache): New static var. (fdbase_clear): New function. Call it whenever removing or renaming directories or symlinks to directories. (fdbase_opendir): New static function. (fdbase, fdbase1): New functions. Call them whenever the code formerly passed chdir_fd to a syscall.
234 lines
5.1 KiB
C
234 lines
5.1 KiB
C
/* Unlink files.
|
|
|
|
Copyright 2009-2025 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 */
|
|
idx_t 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 */
|
|
};
|
|
|
|
static bool
|
|
is_cwd (struct deferred_unlink const *p)
|
|
{
|
|
return p->is_dir && !p->file_name[p->file_name[0] == '.'];
|
|
}
|
|
|
|
/* The unlink queue */
|
|
static struct deferred_unlink *dunlink_head, *dunlink_tail;
|
|
|
|
/* List of entries available for allocation */
|
|
static struct deferred_unlink *dunlink_avail;
|
|
|
|
static struct deferred_unlink *
|
|
dunlink_alloc (void)
|
|
{
|
|
struct deferred_unlink *p = dunlink_avail;
|
|
if (!p)
|
|
return xmalloc (sizeof *p);
|
|
dunlink_avail = p->next;
|
|
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;
|
|
}
|
|
|
|
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;
|
|
idx_t saved_chdir = chdir_current;
|
|
|
|
for (p = dunlink_head; p; )
|
|
{
|
|
struct deferred_unlink *next = p->next;
|
|
|
|
if (force
|
|
|| p->records_written < records_written)
|
|
{
|
|
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;
|
|
|
|
struct fdbase f = fdbase (fname);
|
|
if (f.fd != BADFD && unlinkat (f.fd, f.base, AT_REMOVEDIR) == 0)
|
|
fdbase_clear ();
|
|
else
|
|
{
|
|
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
|
|
{
|
|
struct fdbase f = fdbase (p->file_name);
|
|
if (f.fd != BADFD && unlinkat (f.fd, f.base, 0) == 0)
|
|
fdbase_clear ();
|
|
else if (errno != ENOENT)
|
|
unlink_error (p->file_name);
|
|
}
|
|
dunlink_reclaim (p);
|
|
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;
|
|
|
|
struct fdbase f = fdbase (fname);
|
|
if (f.fd != BADFD && unlinkat (f.fd, f.base, AT_REMOVEDIR) == 0)
|
|
fdbase_clear ();
|
|
else if (errno != ENOENT)
|
|
rmdir_error (fname);
|
|
|
|
dunlink_reclaim (p);
|
|
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)
|
|
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);
|
|
}
|