This closes another race condition, that occurs when overwriting a symlink with a regular file. * NEWS (--dereference consistency): New section. * doc/tar.texi (Option Summary): Describe new --deference behavior. (dereference): Likewise. Remove discussion that I didn't follow, even before --dereference was changed. * src/common.h (deref_stat, set_file_atime): Adjust signatures. * src/compare.c (diff_file, diff_multivol): Respect open_read_flags instead of rolling our own flags. This implements the new behavior for --dereference. (diff_file, diff_dumpdir): Likewise, for fstatat_flags. * src/create.c: Adjust to set_file_atime signature change. * src/extract.c (mark_after_links, file_newer_p, extract_dir): Likewise. * src/incremen.c (try_purge_directory): Likewise. * src/misc.c (maybe_backup_file): Likewise. * src/extract.c (file_newer_p): New arg STP. All callers changed. (maybe_recoverable): New arg REGULAR. All callers changed. Handle the case of overwriting a symlink with a regular file, when --overwrite is specified but --dereference is not. (open_output_file): Add O_CLOEXEC, O_NOCTTY, O_NONBLOCK for consistency with file creation. Add O_NOFOLLOW if overwriting_old_files && ! dereference_option. * src/incremen.c (update_parent_directory): Use fstat, not fstatat; there's less to go wrong. * src/misc.c (deref_stat): Remove DEREF arg. All callers changed. Instead, use fstatat_flags. (set_file_atime): Remove ATFLAG arg. All callers changed. Instead, use fstatat_flags. * src/names.c, src/update.c: Adjust to deref_stat signature change. * src/tar.c (get_date_or_file): Use stat, not deref_stat, as this is not a file to be archived. * tests/Makefile.am (TESTSUITE_AT): Add extrac13.at. * tests/extrac13.at: New file. * tests/testsuite.at: Include it.
1221 lines
29 KiB
C
1221 lines
29 KiB
C
/* Various processing of names.
|
||
|
||
Copyright (C) 1988, 1992, 1994, 1996, 1997, 1998, 1999, 2000, 2001,
|
||
2003, 2004, 2005, 2006, 2007, 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 <system.h>
|
||
|
||
#include <fnmatch.h>
|
||
#include <hash.h>
|
||
#include <quotearg.h>
|
||
|
||
#include "common.h"
|
||
|
||
/* User and group names. */
|
||
|
||
/* Make sure you link with the proper libraries if you are running the
|
||
Yellow Peril (thanks for the good laugh, Ian J.!), or, euh... NIS.
|
||
This code should also be modified for non-UNIX systems to do something
|
||
reasonable. */
|
||
|
||
static char *cached_uname;
|
||
static char *cached_gname;
|
||
|
||
static uid_t cached_uid; /* valid only if cached_uname is not empty */
|
||
static gid_t cached_gid; /* valid only if cached_gname is not empty */
|
||
|
||
/* These variables are valid only if nonempty. */
|
||
static char *cached_no_such_uname;
|
||
static char *cached_no_such_gname;
|
||
|
||
/* These variables are valid only if nonzero. It's not worth optimizing
|
||
the case for weird systems where 0 is not a valid uid or gid. */
|
||
static uid_t cached_no_such_uid;
|
||
static gid_t cached_no_such_gid;
|
||
|
||
static void register_individual_file (char const *name);
|
||
|
||
/* Given UID, find the corresponding UNAME. */
|
||
void
|
||
uid_to_uname (uid_t uid, char **uname)
|
||
{
|
||
struct passwd *passwd;
|
||
|
||
if (uid != 0 && uid == cached_no_such_uid)
|
||
{
|
||
*uname = xstrdup ("");
|
||
return;
|
||
}
|
||
|
||
if (!cached_uname || uid != cached_uid)
|
||
{
|
||
passwd = getpwuid (uid);
|
||
if (passwd)
|
||
{
|
||
cached_uid = uid;
|
||
assign_string (&cached_uname, passwd->pw_name);
|
||
}
|
||
else
|
||
{
|
||
cached_no_such_uid = uid;
|
||
*uname = xstrdup ("");
|
||
return;
|
||
}
|
||
}
|
||
*uname = xstrdup (cached_uname);
|
||
}
|
||
|
||
/* Given GID, find the corresponding GNAME. */
|
||
void
|
||
gid_to_gname (gid_t gid, char **gname)
|
||
{
|
||
struct group *group;
|
||
|
||
if (gid != 0 && gid == cached_no_such_gid)
|
||
{
|
||
*gname = xstrdup ("");
|
||
return;
|
||
}
|
||
|
||
if (!cached_gname || gid != cached_gid)
|
||
{
|
||
group = getgrgid (gid);
|
||
if (group)
|
||
{
|
||
cached_gid = gid;
|
||
assign_string (&cached_gname, group->gr_name);
|
||
}
|
||
else
|
||
{
|
||
cached_no_such_gid = gid;
|
||
*gname = xstrdup ("");
|
||
return;
|
||
}
|
||
}
|
||
*gname = xstrdup (cached_gname);
|
||
}
|
||
|
||
/* Given UNAME, set the corresponding UID and return 1, or else, return 0. */
|
||
int
|
||
uname_to_uid (char const *uname, uid_t *uidp)
|
||
{
|
||
struct passwd *passwd;
|
||
|
||
if (cached_no_such_uname
|
||
&& strcmp (uname, cached_no_such_uname) == 0)
|
||
return 0;
|
||
|
||
if (!cached_uname
|
||
|| uname[0] != cached_uname[0]
|
||
|| strcmp (uname, cached_uname) != 0)
|
||
{
|
||
passwd = getpwnam (uname);
|
||
if (passwd)
|
||
{
|
||
cached_uid = passwd->pw_uid;
|
||
assign_string (&cached_uname, passwd->pw_name);
|
||
}
|
||
else
|
||
{
|
||
assign_string (&cached_no_such_uname, uname);
|
||
return 0;
|
||
}
|
||
}
|
||
*uidp = cached_uid;
|
||
return 1;
|
||
}
|
||
|
||
/* Given GNAME, set the corresponding GID and return 1, or else, return 0. */
|
||
int
|
||
gname_to_gid (char const *gname, gid_t *gidp)
|
||
{
|
||
struct group *group;
|
||
|
||
if (cached_no_such_gname
|
||
&& strcmp (gname, cached_no_such_gname) == 0)
|
||
return 0;
|
||
|
||
if (!cached_gname
|
||
|| gname[0] != cached_gname[0]
|
||
|| strcmp (gname, cached_gname) != 0)
|
||
{
|
||
group = getgrnam (gname);
|
||
if (group)
|
||
{
|
||
cached_gid = group->gr_gid;
|
||
assign_string (&cached_gname, gname);
|
||
}
|
||
else
|
||
{
|
||
assign_string (&cached_no_such_gname, gname);
|
||
return 0;
|
||
}
|
||
}
|
||
*gidp = cached_gid;
|
||
return 1;
|
||
}
|
||
|
||
|
||
static struct name *
|
||
make_name (const char *file_name)
|
||
{
|
||
struct name *p = xzalloc (sizeof (*p));
|
||
if (!file_name)
|
||
file_name = "";
|
||
p->name = xstrdup (file_name);
|
||
p->length = strlen (p->name);
|
||
return p;
|
||
}
|
||
|
||
static void
|
||
free_name (struct name *p)
|
||
{
|
||
if (p)
|
||
{
|
||
free (p->name);
|
||
free (p->caname);
|
||
free (p);
|
||
}
|
||
}
|
||
|
||
|
||
/* Names from the command call. */
|
||
|
||
static struct name *namelist; /* first name in list, if any */
|
||
static struct name *nametail; /* end of name list */
|
||
|
||
/* File name arguments are processed in two stages: first a
|
||
name_array (see below) is filled, then the names from it
|
||
are moved into the namelist.
|
||
|
||
This awkward process is needed only to implement --same-order option,
|
||
which is meant to help process large archives on machines with
|
||
limited memory. With this option on, namelist contains at most one
|
||
entry, which diminishes the memory consumption.
|
||
|
||
However, I very much doubt if we still need this -- Sergey */
|
||
|
||
/* A name_array element contains entries of three types: */
|
||
|
||
#define NELT_NAME 0 /* File name */
|
||
#define NELT_CHDIR 1 /* Change directory request */
|
||
#define NELT_FMASK 2 /* Change fnmatch options request */
|
||
|
||
struct name_elt /* A name_array element. */
|
||
{
|
||
char type; /* Element type, see NELT_* constants above */
|
||
union
|
||
{
|
||
const char *name; /* File or directory name */
|
||
int matching_flags;/* fnmatch options if type == NELT_FMASK */
|
||
} v;
|
||
};
|
||
|
||
static struct name_elt *name_array; /* store an array of names */
|
||
static size_t allocated_entries; /* how big is the array? */
|
||
static size_t entries; /* how many entries does it have? */
|
||
static size_t scanned; /* how many of the entries have we scanned? */
|
||
size_t name_count; /* how many of the entries are names? */
|
||
|
||
/* Check the size of name_array, reallocating it as necessary. */
|
||
static void
|
||
check_name_alloc (void)
|
||
{
|
||
if (entries == allocated_entries)
|
||
{
|
||
if (allocated_entries == 0)
|
||
allocated_entries = 10; /* Set initial allocation */
|
||
name_array = x2nrealloc (name_array, &allocated_entries,
|
||
sizeof (name_array[0]));
|
||
}
|
||
}
|
||
|
||
/* Add to name_array the file NAME with fnmatch options MATCHING_FLAGS */
|
||
void
|
||
name_add_name (const char *name, int matching_flags)
|
||
{
|
||
static int prev_flags = 0; /* FIXME: Or EXCLUDE_ANCHORED? */
|
||
struct name_elt *ep;
|
||
|
||
check_name_alloc ();
|
||
ep = &name_array[entries++];
|
||
if (prev_flags != matching_flags)
|
||
{
|
||
ep->type = NELT_FMASK;
|
||
ep->v.matching_flags = matching_flags;
|
||
prev_flags = matching_flags;
|
||
check_name_alloc ();
|
||
ep = &name_array[entries++];
|
||
}
|
||
ep->type = NELT_NAME;
|
||
ep->v.name = name;
|
||
name_count++;
|
||
}
|
||
|
||
/* Add to name_array a chdir request for the directory NAME */
|
||
void
|
||
name_add_dir (const char *name)
|
||
{
|
||
struct name_elt *ep;
|
||
check_name_alloc ();
|
||
ep = &name_array[entries++];
|
||
ep->type = NELT_CHDIR;
|
||
ep->v.name = name;
|
||
}
|
||
|
||
|
||
/* Names from external name file. */
|
||
|
||
static char *name_buffer; /* buffer to hold the current file name */
|
||
static size_t name_buffer_length; /* allocated length of name_buffer */
|
||
|
||
/* Set up to gather file names for tar. They can either come from a
|
||
file or were saved from decoding arguments. */
|
||
void
|
||
name_init (void)
|
||
{
|
||
name_buffer = xmalloc (NAME_FIELD_SIZE + 2);
|
||
name_buffer_length = NAME_FIELD_SIZE;
|
||
}
|
||
|
||
void
|
||
name_term (void)
|
||
{
|
||
free (name_buffer);
|
||
free (name_array);
|
||
}
|
||
|
||
static int matching_flags; /* exclude_fnmatch options */
|
||
|
||
/* Get the next NELT_NAME element from name_array. Result is in
|
||
static storage and can't be relied upon across two calls.
|
||
|
||
If CHANGE_DIRS is true, treat any entries of type NELT_CHDIR as
|
||
the request to change to the given directory.
|
||
|
||
Entries of type NELT_FMASK cause updates of the matching_flags
|
||
value. */
|
||
static struct name_elt *
|
||
name_next_elt (int change_dirs)
|
||
{
|
||
static struct name_elt entry;
|
||
const char *source;
|
||
char *cursor;
|
||
|
||
while (scanned != entries)
|
||
{
|
||
struct name_elt *ep;
|
||
size_t source_len;
|
||
|
||
ep = &name_array[scanned++];
|
||
if (ep->type == NELT_FMASK)
|
||
{
|
||
matching_flags = ep->v.matching_flags;
|
||
continue;
|
||
}
|
||
|
||
source = ep->v.name;
|
||
source_len = strlen (source);
|
||
if (name_buffer_length < source_len)
|
||
{
|
||
do
|
||
{
|
||
name_buffer_length *= 2;
|
||
if (! name_buffer_length)
|
||
xalloc_die ();
|
||
}
|
||
while (name_buffer_length < source_len);
|
||
|
||
free (name_buffer);
|
||
name_buffer = xmalloc (name_buffer_length + 2);
|
||
}
|
||
strcpy (name_buffer, source);
|
||
|
||
/* Zap trailing slashes. */
|
||
|
||
cursor = name_buffer + strlen (name_buffer) - 1;
|
||
while (cursor > name_buffer && ISSLASH (*cursor))
|
||
*cursor-- = '\0';
|
||
|
||
if (change_dirs && ep->type == NELT_CHDIR)
|
||
{
|
||
if (chdir (name_buffer) < 0)
|
||
chdir_fatal (name_buffer);
|
||
}
|
||
else
|
||
{
|
||
if (unquote_option)
|
||
unquote_string (name_buffer);
|
||
if (incremental_option)
|
||
register_individual_file (name_buffer);
|
||
entry.type = ep->type;
|
||
entry.v.name = name_buffer;
|
||
return &entry;
|
||
}
|
||
}
|
||
|
||
return NULL;
|
||
}
|
||
|
||
const char *
|
||
name_next (int change_dirs)
|
||
{
|
||
struct name_elt *nelt = name_next_elt (change_dirs);
|
||
return nelt ? nelt->v.name : NULL;
|
||
}
|
||
|
||
/* Gather names in a list for scanning. Could hash them later if we
|
||
really care.
|
||
|
||
If the names are already sorted to match the archive, we just read
|
||
them one by one. name_gather reads the first one, and it is called
|
||
by name_match as appropriate to read the next ones. At EOF, the
|
||
last name read is just left in the buffer. This option lets users
|
||
of small machines extract an arbitrary number of files by doing
|
||
"tar t" and editing down the list of files. */
|
||
|
||
void
|
||
name_gather (void)
|
||
{
|
||
/* Buffer able to hold a single name. */
|
||
static struct name *buffer = NULL;
|
||
|
||
struct name_elt *ep;
|
||
|
||
if (same_order_option)
|
||
{
|
||
static int change_dir;
|
||
|
||
while ((ep = name_next_elt (0)) && ep->type == NELT_CHDIR)
|
||
change_dir = chdir_arg (xstrdup (ep->v.name));
|
||
|
||
if (ep)
|
||
{
|
||
free_name (buffer);
|
||
buffer = make_name (ep->v.name);
|
||
buffer->change_dir = change_dir;
|
||
buffer->next = 0;
|
||
buffer->found_count = 0;
|
||
buffer->matching_flags = matching_flags;
|
||
buffer->directory = NULL;
|
||
buffer->parent = NULL;
|
||
buffer->cmdline = true;
|
||
|
||
namelist = nametail = buffer;
|
||
}
|
||
else if (change_dir)
|
||
addname (0, change_dir, false, NULL);
|
||
}
|
||
else
|
||
{
|
||
/* Non sorted names -- read them all in. */
|
||
int change_dir = 0;
|
||
|
||
for (;;)
|
||
{
|
||
int change_dir0 = change_dir;
|
||
while ((ep = name_next_elt (0)) && ep->type == NELT_CHDIR)
|
||
change_dir = chdir_arg (xstrdup (ep->v.name));
|
||
|
||
if (ep)
|
||
addname (ep->v.name, change_dir, true, NULL);
|
||
else
|
||
{
|
||
if (change_dir != change_dir0)
|
||
addname (NULL, change_dir, false, NULL);
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/* Add a name to the namelist. */
|
||
struct name *
|
||
addname (char const *string, int change_dir, bool cmdline, struct name *parent)
|
||
{
|
||
struct name *name = make_name (string);
|
||
|
||
name->prev = nametail;
|
||
name->next = NULL;
|
||
name->found_count = 0;
|
||
name->matching_flags = matching_flags;
|
||
name->change_dir = change_dir;
|
||
name->directory = NULL;
|
||
name->parent = parent;
|
||
name->cmdline = cmdline;
|
||
|
||
if (nametail)
|
||
nametail->next = name;
|
||
else
|
||
namelist = name;
|
||
nametail = name;
|
||
return name;
|
||
}
|
||
|
||
/* Find a match for FILE_NAME (whose string length is LENGTH) in the name
|
||
list. */
|
||
static struct name *
|
||
namelist_match (char const *file_name, size_t length)
|
||
{
|
||
struct name *p;
|
||
|
||
for (p = namelist; p; p = p->next)
|
||
{
|
||
if (p->name[0]
|
||
&& exclude_fnmatch (p->name, file_name, p->matching_flags))
|
||
return p;
|
||
}
|
||
|
||
return NULL;
|
||
}
|
||
|
||
void
|
||
remname (struct name *name)
|
||
{
|
||
struct name *p;
|
||
|
||
if ((p = name->prev) != NULL)
|
||
p->next = name->next;
|
||
else
|
||
namelist = name->next;
|
||
|
||
if ((p = name->next) != NULL)
|
||
p->prev = name->prev;
|
||
else
|
||
nametail = name->prev;
|
||
}
|
||
|
||
/* Return true if and only if name FILE_NAME (from an archive) matches any
|
||
name from the namelist. */
|
||
bool
|
||
name_match (const char *file_name)
|
||
{
|
||
size_t length = strlen (file_name);
|
||
|
||
while (1)
|
||
{
|
||
struct name *cursor = namelist;
|
||
|
||
if (!cursor)
|
||
return true;
|
||
|
||
if (cursor->name[0] == 0)
|
||
{
|
||
chdir_do (cursor->change_dir);
|
||
namelist = NULL;
|
||
nametail = NULL;
|
||
return true;
|
||
}
|
||
|
||
cursor = namelist_match (file_name, length);
|
||
if (cursor)
|
||
{
|
||
if (!(ISSLASH (file_name[cursor->length]) && recursion_option)
|
||
|| cursor->found_count == 0)
|
||
cursor->found_count++; /* remember it matched */
|
||
if (starting_file_option)
|
||
{
|
||
free (namelist);
|
||
namelist = NULL;
|
||
nametail = NULL;
|
||
}
|
||
chdir_do (cursor->change_dir);
|
||
|
||
/* We got a match. */
|
||
return ISFOUND (cursor);
|
||
}
|
||
|
||
/* Filename from archive not found in namelist. If we have the whole
|
||
namelist here, just return 0. Otherwise, read the next name in and
|
||
compare it. If this was the last name, namelist->found_count will
|
||
remain on. If not, we loop to compare the newly read name. */
|
||
|
||
if (same_order_option && namelist->found_count)
|
||
{
|
||
name_gather (); /* read one more */
|
||
if (namelist->found_count)
|
||
return false;
|
||
}
|
||
else
|
||
return false;
|
||
}
|
||
}
|
||
|
||
/* Returns true if all names from the namelist were processed.
|
||
P is the stat_info of the most recently processed entry.
|
||
The decision is postponed until the next entry is read if:
|
||
|
||
1) P ended with a slash (i.e. it was a directory)
|
||
2) P matches any entry from the namelist *and* represents a subdirectory
|
||
or a file lying under this entry (in the terms of directory structure).
|
||
|
||
This is necessary to handle contents of directories. */
|
||
bool
|
||
all_names_found (struct tar_stat_info *p)
|
||
{
|
||
struct name const *cursor;
|
||
size_t len;
|
||
|
||
if (!p->file_name || occurrence_option == 0 || p->had_trailing_slash)
|
||
return false;
|
||
len = strlen (p->file_name);
|
||
for (cursor = namelist; cursor; cursor = cursor->next)
|
||
{
|
||
if ((cursor->name[0] && !WASFOUND (cursor))
|
||
|| (len >= cursor->length && ISSLASH (p->file_name[cursor->length])))
|
||
return false;
|
||
}
|
||
return true;
|
||
}
|
||
|
||
static int
|
||
regex_usage_warning (const char *name)
|
||
{
|
||
static int warned_once = 0;
|
||
|
||
if (warn_regex_usage && fnmatch_pattern_has_wildcards (name, 0))
|
||
{
|
||
warned_once = 1;
|
||
WARN ((0, 0,
|
||
_("Pattern matching characters used in file names")));
|
||
WARN ((0, 0,
|
||
_("Use --wildcards to enable pattern matching,"
|
||
" or --no-wildcards to suppress this warning")));
|
||
}
|
||
return warned_once;
|
||
}
|
||
|
||
/* Print the names of things in the namelist that were not matched. */
|
||
void
|
||
names_notfound (void)
|
||
{
|
||
struct name const *cursor;
|
||
|
||
for (cursor = namelist; cursor; cursor = cursor->next)
|
||
if (!WASFOUND (cursor) && cursor->name[0])
|
||
{
|
||
regex_usage_warning (cursor->name);
|
||
ERROR ((0, 0,
|
||
(cursor->found_count == 0) ?
|
||
_("%s: Not found in archive") :
|
||
_("%s: Required occurrence not found in archive"),
|
||
quotearg_colon (cursor->name)));
|
||
}
|
||
|
||
/* Don't bother freeing the name list; we're about to exit. */
|
||
namelist = NULL;
|
||
nametail = NULL;
|
||
|
||
if (same_order_option)
|
||
{
|
||
const char *name;
|
||
|
||
while ((name = name_next (1)) != NULL)
|
||
{
|
||
regex_usage_warning (name);
|
||
ERROR ((0, 0, _("%s: Not found in archive"),
|
||
quotearg_colon (name)));
|
||
}
|
||
}
|
||
}
|
||
|
||
void
|
||
label_notfound (void)
|
||
{
|
||
struct name const *cursor;
|
||
|
||
if (!namelist)
|
||
return;
|
||
|
||
for (cursor = namelist; cursor; cursor = cursor->next)
|
||
if (WASFOUND (cursor))
|
||
return;
|
||
|
||
if (verbose_option)
|
||
error (0, 0, _("Archive label mismatch"));
|
||
set_exit_status (TAREXIT_DIFFERS);
|
||
|
||
for (cursor = namelist; cursor; cursor = cursor->next)
|
||
{
|
||
if (regex_usage_warning (cursor->name))
|
||
break;
|
||
}
|
||
|
||
/* Don't bother freeing the name list; we're about to exit. */
|
||
namelist = NULL;
|
||
nametail = NULL;
|
||
|
||
if (same_order_option)
|
||
{
|
||
const char *name;
|
||
|
||
while ((name = name_next (1)) != NULL
|
||
&& regex_usage_warning (name) == 0)
|
||
;
|
||
}
|
||
}
|
||
|
||
/* Sorting name lists. */
|
||
|
||
/* Sort *singly* linked LIST of names, of given LENGTH, using COMPARE
|
||
to order names. Return the sorted list. Note that after calling
|
||
this function, the `prev' links in list elements are messed up.
|
||
|
||
Apart from the type `struct name' and the definition of SUCCESSOR,
|
||
this is a generic list-sorting function, but it's too painful to
|
||
make it both generic and portable
|
||
in C. */
|
||
|
||
static struct name *
|
||
merge_sort_sll (struct name *list, int length,
|
||
int (*compare) (struct name const*, struct name const*))
|
||
{
|
||
struct name *first_list;
|
||
struct name *second_list;
|
||
int first_length;
|
||
int second_length;
|
||
struct name *result;
|
||
struct name **merge_point;
|
||
struct name *cursor;
|
||
int counter;
|
||
|
||
# define SUCCESSOR(name) ((name)->next)
|
||
|
||
if (length == 1)
|
||
return list;
|
||
|
||
if (length == 2)
|
||
{
|
||
if ((*compare) (list, SUCCESSOR (list)) > 0)
|
||
{
|
||
result = SUCCESSOR (list);
|
||
SUCCESSOR (result) = list;
|
||
SUCCESSOR (list) = 0;
|
||
return result;
|
||
}
|
||
return list;
|
||
}
|
||
|
||
first_list = list;
|
||
first_length = (length + 1) / 2;
|
||
second_length = length / 2;
|
||
for (cursor = list, counter = first_length - 1;
|
||
counter;
|
||
cursor = SUCCESSOR (cursor), counter--)
|
||
continue;
|
||
second_list = SUCCESSOR (cursor);
|
||
SUCCESSOR (cursor) = 0;
|
||
|
||
first_list = merge_sort_sll (first_list, first_length, compare);
|
||
second_list = merge_sort_sll (second_list, second_length, compare);
|
||
|
||
merge_point = &result;
|
||
while (first_list && second_list)
|
||
if ((*compare) (first_list, second_list) < 0)
|
||
{
|
||
cursor = SUCCESSOR (first_list);
|
||
*merge_point = first_list;
|
||
merge_point = &SUCCESSOR (first_list);
|
||
first_list = cursor;
|
||
}
|
||
else
|
||
{
|
||
cursor = SUCCESSOR (second_list);
|
||
*merge_point = second_list;
|
||
merge_point = &SUCCESSOR (second_list);
|
||
second_list = cursor;
|
||
}
|
||
if (first_list)
|
||
*merge_point = first_list;
|
||
else
|
||
*merge_point = second_list;
|
||
|
||
return result;
|
||
|
||
#undef SUCCESSOR
|
||
}
|
||
|
||
/* Sort doubly linked LIST of names, of given LENGTH, using COMPARE
|
||
to order names. Return the sorted list. */
|
||
static struct name *
|
||
merge_sort (struct name *list, int length,
|
||
int (*compare) (struct name const*, struct name const*))
|
||
{
|
||
struct name *head, *p, *prev;
|
||
head = merge_sort_sll (list, length, compare);
|
||
/* Fixup prev pointers */
|
||
for (prev = NULL, p = head; p; prev = p, p = p->next)
|
||
p->prev = prev;
|
||
return head;
|
||
}
|
||
|
||
/* A comparison function for sorting names. Put found names last;
|
||
break ties by string comparison. */
|
||
|
||
static int
|
||
compare_names_found (struct name const *n1, struct name const *n2)
|
||
{
|
||
int found_diff = WASFOUND (n2) - WASFOUND (n1);
|
||
return found_diff ? found_diff : strcmp (n1->name, n2->name);
|
||
}
|
||
|
||
/* Simple comparison by names. */
|
||
static int
|
||
compare_names (struct name const *n1, struct name const *n2)
|
||
{
|
||
return strcmp (n1->name, n2->name);
|
||
}
|
||
|
||
|
||
/* Add all the dirs under ST to the namelist NAME, descending the
|
||
directory hierarchy recursively. */
|
||
|
||
static void
|
||
add_hierarchy_to_namelist (struct tar_stat_info *st, struct name *name)
|
||
{
|
||
const char *buffer;
|
||
|
||
name->directory = scan_directory (st);
|
||
buffer = directory_contents (name->directory);
|
||
if (buffer)
|
||
{
|
||
struct name *child_head = NULL, *child_tail = NULL;
|
||
size_t name_length = name->length;
|
||
size_t allocated_length = (name_length >= NAME_FIELD_SIZE
|
||
? name_length + NAME_FIELD_SIZE
|
||
: NAME_FIELD_SIZE);
|
||
char *namebuf = xmalloc (allocated_length + 1);
|
||
/* FIXME: + 2 above? */
|
||
const char *string;
|
||
size_t string_length;
|
||
int change_dir = name->change_dir;
|
||
|
||
strcpy (namebuf, name->name);
|
||
if (! ISSLASH (namebuf[name_length - 1]))
|
||
{
|
||
namebuf[name_length++] = '/';
|
||
namebuf[name_length] = '\0';
|
||
}
|
||
|
||
for (string = buffer; *string; string += string_length + 1)
|
||
{
|
||
string_length = strlen (string);
|
||
if (*string == 'D')
|
||
{
|
||
struct name *np;
|
||
struct tar_stat_info subdir;
|
||
int subfd;
|
||
|
||
if (allocated_length <= name_length + string_length)
|
||
{
|
||
do
|
||
{
|
||
allocated_length *= 2;
|
||
if (! allocated_length)
|
||
xalloc_die ();
|
||
}
|
||
while (allocated_length <= name_length + string_length);
|
||
|
||
namebuf = xrealloc (namebuf, allocated_length + 1);
|
||
}
|
||
strcpy (namebuf + name_length, string + 1);
|
||
np = addname (namebuf, change_dir, false, name);
|
||
if (!child_head)
|
||
child_head = np;
|
||
else
|
||
child_tail->sibling = np;
|
||
child_tail = np;
|
||
|
||
tar_stat_init (&subdir);
|
||
subdir.parent = st;
|
||
if (st->fd < 0)
|
||
{
|
||
subfd = -1;
|
||
errno = - st->fd;
|
||
}
|
||
else
|
||
subfd = subfile_open (st, string + 1,
|
||
open_read_flags | O_DIRECTORY);
|
||
if (subfd < 0)
|
||
open_diag (namebuf);
|
||
else
|
||
{
|
||
subdir.fd = subfd;
|
||
if (fstat (subfd, &subdir.stat) != 0)
|
||
stat_diag (namebuf);
|
||
else if (! (O_DIRECTORY || S_ISDIR (subdir.stat.st_mode)))
|
||
{
|
||
errno = ENOTDIR;
|
||
open_diag (namebuf);
|
||
}
|
||
else
|
||
{
|
||
subdir.orig_file_name = xstrdup (namebuf);
|
||
add_hierarchy_to_namelist (&subdir, np);
|
||
restore_parent_fd (&subdir);
|
||
}
|
||
}
|
||
|
||
tar_stat_destroy (&subdir);
|
||
}
|
||
}
|
||
|
||
free (namebuf);
|
||
name->child = child_head;
|
||
}
|
||
}
|
||
|
||
/* Auxiliary functions for hashed table of struct name's. */
|
||
|
||
static size_t
|
||
name_hash (void const *entry, size_t n_buckets)
|
||
{
|
||
struct name const *name = entry;
|
||
return hash_string (name->caname, n_buckets);
|
||
}
|
||
|
||
/* Compare two directories for equality of their names. */
|
||
static bool
|
||
name_compare (void const *entry1, void const *entry2)
|
||
{
|
||
struct name const *name1 = entry1;
|
||
struct name const *name2 = entry2;
|
||
return strcmp (name1->caname, name2->caname) == 0;
|
||
}
|
||
|
||
|
||
/* Rebase `name' member of CHILD and all its siblings to
|
||
the new PARENT. */
|
||
static void
|
||
rebase_child_list (struct name *child, struct name *parent)
|
||
{
|
||
size_t old_prefix_len = child->parent->length;
|
||
size_t new_prefix_len = parent->length;
|
||
char *new_prefix = parent->name;
|
||
|
||
for (; child; child = child->sibling)
|
||
{
|
||
size_t size = child->length - old_prefix_len + new_prefix_len;
|
||
char *newp = xmalloc (size + 1);
|
||
strcpy (newp, new_prefix);
|
||
strcat (newp, child->name + old_prefix_len);
|
||
free (child->name);
|
||
child->name = newp;
|
||
child->length = size;
|
||
|
||
rebase_directory (child->directory,
|
||
child->parent->name, old_prefix_len,
|
||
new_prefix, new_prefix_len);
|
||
}
|
||
}
|
||
|
||
/* Collect all the names from argv[] (or whatever), expand them into a
|
||
directory tree, and sort them. This gets only subdirectories, not
|
||
all files. */
|
||
|
||
void
|
||
collect_and_sort_names (void)
|
||
{
|
||
struct name *name;
|
||
struct name *next_name, *prev_name = NULL;
|
||
int num_names;
|
||
Hash_table *nametab;
|
||
|
||
name_gather ();
|
||
|
||
if (!namelist)
|
||
addname (".", 0, false, NULL);
|
||
|
||
if (listed_incremental_option)
|
||
{
|
||
switch (chdir_count ())
|
||
{
|
||
case 0:
|
||
break;
|
||
|
||
case 1:
|
||
if (namelist->change_dir == 0)
|
||
USAGE_ERROR ((0, 0,
|
||
_("Using -C option inside file list is not "
|
||
"allowed with --listed-incremental")));
|
||
break;
|
||
|
||
default:
|
||
USAGE_ERROR ((0, 0,
|
||
_("Only one -C option is allowed with "
|
||
"--listed-incremental")));
|
||
}
|
||
|
||
read_directory_file ();
|
||
}
|
||
|
||
num_names = 0;
|
||
for (name = namelist; name; name = name->next, num_names++)
|
||
{
|
||
struct tar_stat_info st;
|
||
|
||
if (name->found_count || name->directory)
|
||
continue;
|
||
if (name->matching_flags & EXCLUDE_WILDCARDS)
|
||
/* NOTE: EXCLUDE_ANCHORED is not relevant here */
|
||
/* FIXME: just skip regexps for now */
|
||
continue;
|
||
chdir_do (name->change_dir);
|
||
|
||
if (name->name[0] == 0)
|
||
continue;
|
||
|
||
tar_stat_init (&st);
|
||
|
||
if (deref_stat (name->name, &st.stat) != 0)
|
||
{
|
||
stat_diag (name->name);
|
||
continue;
|
||
}
|
||
if (S_ISDIR (st.stat.st_mode))
|
||
{
|
||
int dir_fd = openat (chdir_fd, name->name,
|
||
open_read_flags | O_DIRECTORY);
|
||
if (dir_fd < 0)
|
||
open_diag (name->name);
|
||
else
|
||
{
|
||
st.fd = dir_fd;
|
||
if (fstat (dir_fd, &st.stat) != 0)
|
||
stat_diag (name->name);
|
||
else if (O_DIRECTORY || S_ISDIR (st.stat.st_mode))
|
||
{
|
||
st.orig_file_name = xstrdup (name->name);
|
||
name->found_count++;
|
||
add_hierarchy_to_namelist (&st, name);
|
||
}
|
||
}
|
||
}
|
||
|
||
tar_stat_destroy (&st);
|
||
}
|
||
|
||
namelist = merge_sort (namelist, num_names, compare_names);
|
||
|
||
num_names = 0;
|
||
nametab = hash_initialize (0, 0,
|
||
name_hash,
|
||
name_compare, NULL);
|
||
for (name = namelist; name; name = next_name)
|
||
{
|
||
next_name = name->next;
|
||
name->caname = normalize_filename (name->name);
|
||
if (prev_name)
|
||
{
|
||
struct name *p = hash_lookup (nametab, name);
|
||
if (p)
|
||
{
|
||
/* Keep the one listed in the command line */
|
||
if (!name->parent)
|
||
{
|
||
if (p->child)
|
||
rebase_child_list (p->child, name);
|
||
hash_delete (nametab, name);
|
||
/* FIXME: remove_directory (p->caname); ? */
|
||
remname (p);
|
||
free_name (p);
|
||
num_names--;
|
||
}
|
||
else
|
||
{
|
||
if (name->child)
|
||
rebase_child_list (name->child, p);
|
||
/* FIXME: remove_directory (name->caname); ? */
|
||
remname (name);
|
||
free_name (name);
|
||
continue;
|
||
}
|
||
}
|
||
}
|
||
name->found_count = 0;
|
||
if (!hash_insert (nametab, name))
|
||
xalloc_die ();
|
||
prev_name = name;
|
||
num_names++;
|
||
}
|
||
nametail = prev_name;
|
||
hash_free (nametab);
|
||
|
||
namelist = merge_sort (namelist, num_names, compare_names_found);
|
||
|
||
if (listed_incremental_option)
|
||
{
|
||
for (name = namelist; name && name->name[0] == 0; name++)
|
||
;
|
||
if (name)
|
||
append_incremental_renames (name->directory);
|
||
}
|
||
}
|
||
|
||
/* This is like name_match, except that
|
||
1. It returns a pointer to the name it matched, and doesn't set FOUND
|
||
in structure. The caller will have to do that if it wants to.
|
||
2. If the namelist is empty, it returns null, unlike name_match, which
|
||
returns TRUE. */
|
||
struct name *
|
||
name_scan (const char *file_name)
|
||
{
|
||
size_t length = strlen (file_name);
|
||
|
||
while (1)
|
||
{
|
||
struct name *cursor = namelist_match (file_name, length);
|
||
if (cursor)
|
||
return cursor;
|
||
|
||
/* Filename from archive not found in namelist. If we have the whole
|
||
namelist here, just return 0. Otherwise, read the next name in and
|
||
compare it. If this was the last name, namelist->found_count will
|
||
remain on. If not, we loop to compare the newly read name. */
|
||
|
||
if (same_order_option && namelist && namelist->found_count)
|
||
{
|
||
name_gather (); /* read one more */
|
||
if (namelist->found_count)
|
||
return 0;
|
||
}
|
||
else
|
||
return 0;
|
||
}
|
||
}
|
||
|
||
/* This returns a name from the namelist which doesn't have ->found
|
||
set. It sets ->found before returning, so successive calls will
|
||
find and return all the non-found names in the namelist. */
|
||
struct name *gnu_list_name;
|
||
|
||
struct name const *
|
||
name_from_list ()
|
||
{
|
||
if (!gnu_list_name)
|
||
gnu_list_name = namelist;
|
||
while (gnu_list_name
|
||
&& (gnu_list_name->found_count || gnu_list_name->name[0] == 0))
|
||
gnu_list_name = gnu_list_name->next;
|
||
if (gnu_list_name)
|
||
{
|
||
gnu_list_name->found_count++;
|
||
chdir_do (gnu_list_name->change_dir);
|
||
return gnu_list_name;
|
||
}
|
||
return NULL;
|
||
}
|
||
|
||
void
|
||
blank_name_list (void)
|
||
{
|
||
struct name *name;
|
||
|
||
gnu_list_name = 0;
|
||
for (name = namelist; name; name = name->next)
|
||
name->found_count = 0;
|
||
}
|
||
|
||
/* Yield a newly allocated file name consisting of FILE_NAME concatenated to
|
||
NAME, with an intervening slash if FILE_NAME does not already end in one. */
|
||
char *
|
||
new_name (const char *file_name, const char *name)
|
||
{
|
||
size_t file_name_len = strlen (file_name);
|
||
size_t namesize = strlen (name) + 1;
|
||
int slash = file_name_len && ! ISSLASH (file_name[file_name_len - 1]);
|
||
char *buffer = xmalloc (file_name_len + slash + namesize);
|
||
memcpy (buffer, file_name, file_name_len);
|
||
buffer[file_name_len] = '/';
|
||
memcpy (buffer + file_name_len + slash, name, namesize);
|
||
return buffer;
|
||
}
|
||
|
||
/* Return nonzero if file NAME is excluded. */
|
||
bool
|
||
excluded_name (char const *name)
|
||
{
|
||
return excluded_file_name (excluded, name + FILE_SYSTEM_PREFIX_LEN (name));
|
||
}
|
||
|
||
static Hash_table *individual_file_table;
|
||
|
||
static void
|
||
register_individual_file (char const *name)
|
||
{
|
||
struct stat st;
|
||
|
||
if (deref_stat (name, &st) != 0)
|
||
return; /* Will be complained about later */
|
||
if (S_ISDIR (st.st_mode))
|
||
return;
|
||
|
||
hash_string_insert (&individual_file_table, name);
|
||
}
|
||
|
||
bool
|
||
is_individual_file (char const *name)
|
||
{
|
||
return hash_string_lookup (individual_file_table, name);
|
||
}
|
||
|
||
|
||
|
||
/* Return the size of the prefix of FILE_NAME that is removed after
|
||
stripping NUM leading file name components. NUM must be
|
||
positive. */
|
||
|
||
size_t
|
||
stripped_prefix_len (char const *file_name, size_t num)
|
||
{
|
||
char const *p = file_name + FILE_SYSTEM_PREFIX_LEN (file_name);
|
||
while (ISSLASH (*p))
|
||
p++;
|
||
while (*p)
|
||
{
|
||
bool slash = ISSLASH (*p);
|
||
p++;
|
||
if (slash)
|
||
{
|
||
if (--num == 0)
|
||
return p - file_name;
|
||
while (ISSLASH (*p))
|
||
p++;
|
||
}
|
||
}
|
||
return -1;
|
||
}
|
||
|
||
/* Return nonzero if NAME contains ".." as a file name component. */
|
||
bool
|
||
contains_dot_dot (char const *name)
|
||
{
|
||
char const *p = name + FILE_SYSTEM_PREFIX_LEN (name);
|
||
|
||
for (;; p++)
|
||
{
|
||
if (p[0] == '.' && p[1] == '.' && (ISSLASH (p[2]) || !p[2]))
|
||
return 1;
|
||
|
||
while (! ISSLASH (*p))
|
||
{
|
||
if (! *p++)
|
||
return 0;
|
||
}
|
||
}
|
||
}
|