tar: --dereference consistency

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.
This commit is contained in:
Paul Eggert
2010-09-23 19:41:05 -07:00
parent efe26f98ec
commit 14efeb9f95
14 changed files with 195 additions and 122 deletions

View File

@@ -611,7 +611,7 @@ int remove_any_file (const char *file_name, enum remove_option option);
bool maybe_backup_file (const char *file_name, bool this_is_the_archive);
void undo_last_backup (void);
int deref_stat (bool deref, char const *name, struct stat *buf);
int deref_stat (char const *name, struct stat *buf);
extern int chdir_current;
extern int chdir_fd;
@@ -640,7 +640,7 @@ void xpipe (int fd[2]);
void *page_aligned_alloc (void **ptr, size_t size);
int set_file_atime (int fd, int parentfd, char const *file,
struct timespec atime, int atflag);
struct timespec atime);
/* Module names.c. */

View File

@@ -151,7 +151,7 @@ read_and_process (struct tar_stat_info *st, int (*processor) (size_t, char *))
static int
get_stat_data (char const *file_name, struct stat *stat_data)
{
int status = deref_stat (dereference_option, file_name, stat_data);
int status = deref_stat (file_name, stat_data);
if (status != 0)
{
@@ -217,14 +217,7 @@ diff_file (void)
}
else
{
int atime_flag =
(atime_preserve_option == system_atime_preserve
? O_NOATIME
: 0);
diff_handle = openat (chdir_fd, file_name,
(O_RDONLY | O_BINARY | O_CLOEXEC | O_NOCTTY
| O_NONBLOCK | atime_flag));
diff_handle = openat (chdir_fd, file_name, open_read_flags);
if (diff_handle < 0)
{
@@ -244,8 +237,7 @@ diff_file (void)
if (atime_preserve_option == replace_atime_preserve)
{
struct timespec atime = get_stat_atime (&stat_data);
if (set_file_atime (diff_handle, chdir_fd, file_name,
atime, 0)
if (set_file_atime (diff_handle, chdir_fd, file_name, atime)
!= 0)
utime_error (file_name);
}
@@ -372,7 +364,7 @@ diff_dumpdir (void)
dev_t dev = 0;
struct stat stat_data;
if (deref_stat (true, current_stat_info.file_name, &stat_data))
if (deref_stat (current_stat_info.file_name, &stat_data) != 0)
{
if (errno == ENOENT)
stat_warn (current_stat_info.file_name);
@@ -399,10 +391,6 @@ diff_multivol (void)
struct stat stat_data;
int fd, status;
off_t offset;
int atime_flag =
(atime_preserve_option == system_atime_preserve
? O_NOATIME
: 0);
if (current_stat_info.had_trailing_slash)
{
@@ -429,9 +417,7 @@ diff_multivol (void)
}
fd = openat (chdir_fd, current_stat_info.file_name,
(O_RDONLY | O_BINARY | O_CLOEXEC | O_NOCTTY | O_NONBLOCK
| atime_flag));
fd = openat (chdir_fd, current_stat_info.file_name, open_read_flags);
if (fd < 0)
{

View File

@@ -1795,9 +1795,7 @@ dump_file0 (struct tar_stat_info *st, char const *name, char const *p)
set_exit_status (TAREXIT_DIFFERS);
}
else if (atime_preserve_option == replace_atime_preserve
&& (set_file_atime (fd, parentfd, name,
st->atime, fstatat_flags)
!= 0))
&& set_file_atime (fd, parentfd, name, st->atime) != 0)
utime_error (p);
}

View File

@@ -375,7 +375,7 @@ mark_after_links (struct delayed_set_stat *head)
struct stat st;
h->after_links = 1;
if (fstatat (chdir_fd, h->file_name, &st, 0) != 0)
if (deref_stat (h->file_name, &st) != 0)
stat_error (h->file_name);
else
{
@@ -548,27 +548,32 @@ make_directories (char *file_name)
return did_something; /* tell them to retry if we made one */
}
/* Return true if FILE_NAME (with status *STP, if STP) is not a
directory, and has a time stamp newer than (or equal to) that of
TAR_STAT. */
static bool
file_newer_p (const char *file_name, struct tar_stat_info *tar_stat)
file_newer_p (const char *file_name, struct stat const *stp,
struct tar_stat_info *tar_stat)
{
struct stat st;
if (fstatat (chdir_fd, file_name, &st, 0))
if (!stp)
{
if (errno != ENOENT)
if (deref_stat (file_name, &st) != 0)
{
stat_warn (file_name);
/* Be on the safe side: if the file does exist assume it is newer */
return true;
if (errno != ENOENT)
{
stat_warn (file_name);
/* Be safer: if the file exists, assume it is newer. */
return true;
}
return false;
}
return false;
stp = &st;
}
if (!S_ISDIR (st.st_mode)
&& tar_timespec_cmp (tar_stat->mtime, get_stat_mtime (&st)) <= 0)
{
return true;
}
return false;
return (! S_ISDIR (stp->st_mode)
&& tar_timespec_cmp (tar_stat->mtime, get_stat_mtime (stp)) <= 0);
}
#define RECOVER_NO 0
@@ -580,18 +585,39 @@ file_newer_p (const char *file_name, struct tar_stat_info *tar_stat)
Return RECOVER_OK if we somewhat increased our chances at a successful
extraction, RECOVER_NO if there are no chances, and RECOVER_SKIP if the
caller should skip extraction of that member. The value of errno is
properly restored on returning RECOVER_NO. */
properly restored on returning RECOVER_NO.
If REGULAR, the caller was trying to extract onto a regular file.
Set *INTERDIR_MADE if an intermediate directory is made as part of
the recovery process. */
static int
maybe_recoverable (char *file_name, bool *interdir_made)
maybe_recoverable (char *file_name, bool regular, bool *interdir_made)
{
int e = errno;
struct stat st;
struct stat const *stp = 0;
if (*interdir_made)
return RECOVER_NO;
switch (errno)
switch (e)
{
case ELOOP:
if (! regular
|| old_files_option != OVERWRITE_OLD_FILES || dereference_option)
break;
if (strchr (file_name, '/'))
{
if (deref_stat (file_name, &st) != 0)
break;
stp = &st;
}
/* The caller tried to open a symbolic link with O_NOFOLLOW.
Fall through, treating it as an already-existing file. */
case EEXIST:
/* Remove an old file, if the options allow this. */
@@ -601,21 +627,16 @@ maybe_recoverable (char *file_name, bool *interdir_made)
return RECOVER_SKIP;
case KEEP_NEWER_FILES:
if (file_newer_p (file_name, &current_stat_info))
{
errno = e;
return RECOVER_NO;
}
if (file_newer_p (file_name, stp, &current_stat_info))
break;
/* FALL THROUGH */
case DEFAULT_OLD_FILES:
case NO_OVERWRITE_DIR_OLD_FILES:
case OVERWRITE_OLD_FILES:
{
int r = remove_any_file (file_name, ORDINARY_REMOVE_OPTION);
errno = EEXIST;
return r > 0 ? RECOVER_OK : RECOVER_NO;
}
if (0 < remove_any_file (file_name, ORDINARY_REMOVE_OPTION))
return RECOVER_OK;
break;
case UNLINK_FIRST_OLD_FILES:
break;
@@ -623,19 +644,20 @@ maybe_recoverable (char *file_name, bool *interdir_made)
case ENOENT:
/* Attempt creating missing intermediate directories. */
if (! make_directories (file_name))
if (make_directories (file_name))
{
errno = ENOENT;
return RECOVER_NO;
*interdir_made = true;
return RECOVER_OK;
}
*interdir_made = true;
return RECOVER_OK;
break;
default:
/* Just say we can't do anything about it... */
return RECOVER_NO;
break;
}
errno = e;
return RECOVER_NO;
}
/* Fix the statuses of all directories whose statuses need fixing, and
@@ -769,7 +791,7 @@ extract_dir (char *file_name, int typeflag)
|| old_files_option == OVERWRITE_OLD_FILES))
{
struct stat st;
if (fstatat (chdir_fd, file_name, &st, 0) == 0)
if (deref_stat (file_name, &st) == 0)
{
current_mode = st.st_mode;
current_mode_mask = ALL_MODE_BITS;
@@ -787,7 +809,7 @@ extract_dir (char *file_name, int typeflag)
errno = EEXIST;
}
switch (maybe_recoverable (file_name, &interdir_made))
switch (maybe_recoverable (file_name, false, &interdir_made))
{
case RECOVER_OK:
continue;
@@ -823,8 +845,11 @@ open_output_file (char const *file_name, int typeflag, mode_t mode,
{
int fd;
bool overwriting_old_files = old_files_option == OVERWRITE_OLD_FILES;
int openflag = (O_WRONLY | O_BINARY | O_CREAT
| (overwriting_old_files ? O_TRUNC : O_EXCL));
int openflag = (O_WRONLY | O_BINARY | O_CLOEXEC | O_NOCTTY | O_NONBLOCK
| O_CREAT
| (overwriting_old_files
? O_TRUNC | (dereference_option ? 0 : O_NOFOLLOW)
: O_EXCL));
if (typeflag == CONTTYPE)
{
@@ -898,21 +923,19 @@ extract_file (char *file_name, int typeflag)
}
else
{
int recover = RECOVER_NO;
do
fd = open_output_file (file_name, typeflag, mode,
&current_mode, &current_mode_mask);
while (fd < 0
&& (recover = maybe_recoverable (file_name, &interdir_made))
== RECOVER_OK);
if (fd < 0)
while ((fd = open_output_file (file_name, typeflag, mode,
&current_mode, &current_mode_mask))
< 0)
{
skip_member ();
if (recover == RECOVER_SKIP)
return 0;
open_error (file_name);
return 1;
int recover = maybe_recoverable (file_name, true, &interdir_made);
if (recover != RECOVER_OK)
{
skip_member ();
if (recover == RECOVER_SKIP)
return 0;
open_error (file_name);
return 1;
}
}
}
@@ -994,7 +1017,7 @@ create_placeholder_file (char *file_name, bool is_symlink, bool *interdir_made)
while ((fd = openat (chdir_fd, file_name, O_WRONLY | O_CREAT | O_EXCL, 0)) < 0)
{
switch (maybe_recoverable (file_name, interdir_made))
switch (maybe_recoverable (file_name, false, interdir_made))
{
case RECOVER_OK:
continue;
@@ -1106,7 +1129,8 @@ extract_link (char *file_name, int typeflag)
errno = e;
}
while ((rc = maybe_recoverable (file_name, &interdir_made)) == RECOVER_OK);
while ((rc = maybe_recoverable (file_name, false, &interdir_made))
== RECOVER_OK);
if (rc == RECOVER_SKIP)
return 0;
@@ -1130,7 +1154,7 @@ extract_symlink (char *file_name, int typeflag)
return create_placeholder_file (file_name, true, &interdir_made);
while (symlinkat (current_stat_info.link_name, chdir_fd, file_name) != 0)
switch (maybe_recoverable (file_name, &interdir_made))
switch (maybe_recoverable (file_name, false, &interdir_made))
{
case RECOVER_OK:
continue;
@@ -1171,7 +1195,7 @@ extract_node (char *file_name, int typeflag)
while (mknodat (chdir_fd, file_name, mode, current_stat_info.stat.st_rdev)
!= 0)
switch (maybe_recoverable (file_name, &interdir_made))
switch (maybe_recoverable (file_name, false, &interdir_made))
{
case RECOVER_OK:
continue;
@@ -1200,7 +1224,7 @@ extract_fifo (char *file_name, int typeflag)
& ~ (0 < same_owner_option ? S_IRWXG | S_IRWXO : 0));
while (mkfifoat (chdir_fd, file_name, mode) != 0)
switch (maybe_recoverable (file_name, &interdir_made))
switch (maybe_recoverable (file_name, false, &interdir_made))
{
case RECOVER_OK:
continue;
@@ -1348,7 +1372,7 @@ prepare_to_extract (char const *file_name, int typeflag, tar_extractor_t *fun)
break;
case KEEP_NEWER_FILES:
if (file_newer_p (file_name, &current_stat_info))
if (file_newer_p (file_name, 0, &current_stat_info))
{
WARNOPT (WARN_IGNORE_NEWER,
(0, 0, _("Current %s is newer or same age"),

View File

@@ -408,7 +408,7 @@ update_parent_directory (struct tar_stat_info *parent)
if (directory)
{
struct stat st;
if (fstatat (parent->fd, ".", &st, fstatat_flags) != 0)
if (fstat (parent->fd, &st) != 0)
stat_diag (directory->name);
else
directory->mtime = get_stat_mtime (&st);
@@ -1668,7 +1668,7 @@ try_purge_directory (char const *directory_name)
free (p);
p = new_name (directory_name, cur);
if (deref_stat (false, p, &st))
if (deref_stat (p, &st) != 0)
{
if (errno != ENOENT) /* FIXME: Maybe keep a list of renamed
dirs and check it here? */

View File

@@ -544,7 +544,7 @@ maybe_backup_file (const char *file_name, bool this_is_the_archive)
if (this_is_the_archive && _remdev (file_name))
return true;
if (fstatat (chdir_fd, file_name, &file_stat, 0))
if (deref_stat (file_name, &file_stat) != 0)
{
if (errno == ENOENT)
return true;
@@ -608,24 +608,24 @@ undo_last_backup (void)
}
}
/* Depending on DEREF, apply either stat or lstat to (NAME, BUF). */
/* Apply either stat or lstat to (NAME, BUF), depending on the
presence of the --dereference option. NAME is relative to the
most-recent argument to chdir_do. */
int
deref_stat (bool deref, char const *name, struct stat *buf)
deref_stat (char const *name, struct stat *buf)
{
return fstatat (chdir_fd, name, buf, deref ? 0 : AT_SYMLINK_NOFOLLOW);
return fstatat (chdir_fd, name, buf, fstatat_flags);
}
/* Set FD's (i.e., assuming the working directory is PARENTFD, FILE's)
access time to ATIME. ATFLAG controls symbolic-link following, in
the style of openat. */
access time to ATIME. */
int
set_file_atime (int fd, int parentfd, char const *file, struct timespec atime,
int atflag)
set_file_atime (int fd, int parentfd, char const *file, struct timespec atime)
{
struct timespec ts[2];
ts[0] = atime;
ts[1].tv_nsec = UTIME_OMIT;
return fdutimensat (fd, parentfd, file, ts, atflag);
return fdutimensat (fd, parentfd, file, ts, fstatat_flags);
}
/* A description of a working directory. */

View File

@@ -981,7 +981,7 @@ collect_and_sort_names (void)
tar_stat_init (&st);
if (deref_stat (dereference_option, name->name, &st.stat) != 0)
if (deref_stat (name->name, &st.stat) != 0)
{
stat_diag (name->name);
continue;
@@ -1159,7 +1159,7 @@ register_individual_file (char const *name)
{
struct stat st;
if (deref_stat (dereference_option, name, &st) != 0)
if (deref_stat (name, &st) != 0)
return; /* Will be complained about later */
if (S_ISDIR (st.st_mode))
return;

View File

@@ -1014,7 +1014,7 @@ get_date_or_file (struct tar_args *args, const char *option,
|| *str == '.')
{
struct stat st;
if (deref_stat (dereference_option, str, &st) != 0)
if (stat (str, &st) != 0)
{
stat_error (str);
USAGE_ERROR ((0, 0, _("Date sample file not found")));

View File

@@ -138,8 +138,7 @@ update_archive (void)
struct stat s;
chdir_do (name->change_dir);
if (deref_stat (dereference_option,
current_stat_info.file_name, &s) == 0)
if (deref_stat (current_stat_info.file_name, &s) == 0)
{
if (S_ISDIR (s.st_mode))
{