1
0
mirror of git://git.sv.gnu.org/coreutils.git synced 2026-04-13 07:15:50 +02:00

Don't include dirname.h, since system.h does it now.

(cache_fstatat, cache_stat_init): New functions.
(cache_statted, cache_stat_ok): New functions.
(write_protected_non_symlink): Remove struct stat ** buf_p arg,
which is no longer needed with the new functions.  All callers
changed.
(prompt, is_dir_lstat, remove_entry, remove_dir):
New struct stat * arg.  All callers changed.
(write_protected_non_symlink, prompt, is_dir_lstat, remove_entry):
(remove_cwd_entries, remove_dir, rm_1):
Use and maintain the file status cache.
(prompt, remove_entry): Omit the first "directory" in the diagnostic
"Cannot remove directory `foo': is a directory".  This causes "rm"
to pass a test case that it would otherwise fail now that it
"knows" more about its argument.  I think the diagnostic is better
without the first "directory" anyway.
(prompt): Remove the no-longer-needed IS_DIR arg; all callers changed.
(rm_1): Reject attempts to remove /, ./, or ../.
This commit is contained in:
Paul Eggert
2006-09-03 02:54:51 +00:00
parent fcf498b1b4
commit e5fc4f24aa

View File

@@ -26,7 +26,6 @@
#include "system.h"
#include "cycle-check.h"
#include "dirfd.h"
#include "dirname.h"
#include "error.h"
#include "euidaccess.h"
#include "euidaccess-stat.h"
@@ -152,6 +151,43 @@ close_preserve_errno (int fd)
return result;
}
/* Like fstatat, but cache the result. If ST->st_size is -1, the
status has not been gotten yet. If less than -1, fstatat failed
with errno == -1 - ST->st_size. Otherwise, the status has already
been gotten, so return 0. */
static int
cache_fstatat (int fd, char const *file, struct stat *st, int flag)
{
if (st->st_size == -1 && fstatat (fd, file, st, flag) != 0)
st->st_size = -1 - errno;
if (0 <= st->st_size)
return 0;
errno = -1 - st->st_size;
return -1;
}
/* Initialize a fstatat cache *ST. */
static inline void
cache_stat_init (struct stat *st)
{
st->st_size = -1;
}
/* Return true if *ST has been statted. */
static inline bool
cache_statted (struct stat *st)
{
return (st->st_size != -1);
}
/* Return true if *ST has been statted successfully. */
static inline bool
cache_stat_ok (struct stat *st)
{
return (0 <= st->st_size);
}
static void
hash_freer (void *x)
{
@@ -661,18 +697,16 @@ is_empty_dir (int fd_cwd, char const *dir)
/* Return true if FILE is determined to be an unwritable non-symlink.
Otherwise, return false (including when lstat'ing it fails).
If lstat (aka fstatat) succeeds, set *BUF_P to BUF.
Set *BUF to the file status.
This is to avoid calling euidaccess when FILE is a symlink. */
static bool
write_protected_non_symlink (int fd_cwd,
char const *file,
Dirstack_state const *ds,
struct stat **buf_p,
struct stat *buf)
{
if (fstatat (fd_cwd, file, buf, AT_SYMLINK_NOFOLLOW) != 0)
if (cache_fstatat (fd_cwd, file, buf, AT_SYMLINK_NOFOLLOW) != 0)
return false;
*buf_p = buf;
if (S_ISLNK (buf->st_mode))
return false;
/* Here, we know FILE is not a symbolic link. */
@@ -744,41 +778,33 @@ write_protected_non_symlink (int fd_cwd,
directory FILENAME. MODE is ignored when FILENAME is not a directory.
Set *IS_EMPTY to T_YES if FILENAME is an empty directory, and it is
appropriate to try to remove it with rmdir (e.g. recursive mode).
Don't even try to set *IS_EMPTY when MODE == PA_REMOVE_DIR.
Set *IS_DIR to T_YES or T_NO if we happen to determine whether
FILENAME is a directory. */
Don't even try to set *IS_EMPTY when MODE == PA_REMOVE_DIR. */
static enum RM_status
prompt (int fd_cwd, Dirstack_state const *ds, char const *filename,
struct stat *sbuf,
struct rm_options const *x, enum Prompt_action mode,
Ternary *is_dir, Ternary *is_empty)
Ternary *is_empty)
{
bool write_protected = false;
struct stat *sbuf = NULL;
struct stat buf;
*is_empty = T_UNKNOWN;
*is_dir = T_UNKNOWN;
if (((!x->ignore_missing_files & (x->interactive | x->stdin_tty))
&& (write_protected = write_protected_non_symlink (fd_cwd, filename,
ds, &sbuf, &buf)))
ds, sbuf)))
|| x->interactive)
{
if (sbuf == NULL)
if (cache_fstatat (fd_cwd, filename, sbuf, AT_SYMLINK_NOFOLLOW) != 0)
{
sbuf = &buf;
if (fstatat (fd_cwd, filename, sbuf, AT_SYMLINK_NOFOLLOW))
{
/* lstat failed. This happens e.g., with `rm '''. */
error (0, errno, _("cannot remove %s"),
quote (full_filename (filename)));
return RM_ERROR;
}
/* This happens, e.g., with `rm '''. */
error (0, errno, _("cannot remove %s"),
quote (full_filename (filename)));
return RM_ERROR;
}
if (S_ISDIR (sbuf->st_mode) && !x->recursive)
{
error (0, EISDIR, _("cannot remove directory %s"),
error (0, EISDIR, _("cannot remove %s"),
quote (full_filename (filename)));
return RM_ERROR;
}
@@ -795,8 +821,6 @@ prompt (int fd_cwd, Dirstack_state const *ds, char const *filename,
{
char const *quoted_name = quote (full_filename (filename));
*is_dir = (S_ISDIR (sbuf->st_mode) ? T_YES : T_NO);
/* FIXME: use a variant of error (instead of fprintf) that doesn't
append a newline. Then we won't have to declare program_name in
this file. */
@@ -832,13 +856,15 @@ prompt (int fd_cwd, Dirstack_state const *ds, char const *filename,
/* Return true if FILENAME is a directory (and not a symlink to a directory).
Otherwise, including the case in which lstat fails, return false.
*ST is FILENAME's tstatus.
Do not modify errno. */
static inline bool
is_dir_lstat (char const *filename)
is_dir_lstat (char const *filename, struct stat *st)
{
struct stat sbuf;
int saved_errno = errno;
bool is_dir = lstat (filename, &sbuf) == 0 && S_ISDIR (sbuf.st_mode);
bool is_dir =
(cache_fstatat (AT_FDCWD, filename, st, AT_SYMLINK_NOFOLLOW) == 0
&& S_ISDIR (st->st_mode));
errno = saved_errno;
return is_dir;
}
@@ -896,12 +922,13 @@ is_dir_lstat (char const *filename)
static enum RM_status
remove_entry (int fd_cwd, Dirstack_state const *ds, char const *filename,
struct stat *st,
struct rm_options const *x, struct dirent const *dp)
{
Ternary is_dir;
Ternary is_empty_directory;
enum RM_status s = prompt (fd_cwd, ds, filename, x, PA_DESCEND_INTO_DIR,
&is_dir, &is_empty_directory);
enum RM_status s = prompt (fd_cwd, ds, filename, st, x, PA_DESCEND_INTO_DIR,
&is_empty_directory);
bool known_to_be_dir = (cache_stat_ok (st) && S_ISDIR (st->st_mode));
if (s != RM_OK)
return s;
@@ -922,9 +949,9 @@ remove_entry (int fd_cwd, Dirstack_state const *ds, char const *filename,
if (cannot_unlink_dir ())
{
if (is_dir == T_YES && ! x->recursive)
if (known_to_be_dir && ! x->recursive)
{
error (0, EISDIR, _("cannot remove directory %s"),
error (0, EISDIR, _("cannot remove %s"),
quote (full_filename (filename)));
return RM_ERROR;
}
@@ -941,7 +968,7 @@ remove_entry (int fd_cwd, Dirstack_state const *ds, char const *filename,
unlink call. If FILENAME is a command-line argument, then dp is NULL,
so we'll first try to unlink it. Using unlink here is ok, because it
cannot remove a directory. */
if ((dp && DT_MUST_BE (dp, DT_DIR)) || is_dir == T_YES)
if ((dp && DT_MUST_BE (dp, DT_DIR)) || known_to_be_dir)
return RM_NONEMPTY_DIR;
DO_UNLINK (fd_cwd, filename, x);
@@ -949,7 +976,7 @@ remove_entry (int fd_cwd, Dirstack_state const *ds, char const *filename,
/* Upon a failed attempt to unlink a directory, most non-Linux systems
set errno to the POSIX-required value EPERM. In that case, change
errno to EISDIR so that we emit a better diagnostic. */
if (! x->recursive && errno == EPERM && is_dir_lstat (filename))
if (! x->recursive && errno == EPERM && is_dir_lstat (filename, st))
errno = EISDIR;
if (! x->recursive
@@ -965,16 +992,20 @@ remove_entry (int fd_cwd, Dirstack_state const *ds, char const *filename,
}
else
{
/* If we don't already know whether FILENAME is a directory, find out now.
Then, if it's a non-directory, we can use unlink on it. */
if (is_dir == T_UNKNOWN)
/* If we don't already know whether FILENAME is a directory,
find out now. Then, if it's a non-directory, we can use
unlink on it. */
bool is_dir;
if (cache_statted (st))
is_dir = known_to_be_dir;
else
{
if (dp && DT_IS_KNOWN (dp))
is_dir = DT_MUST_BE (dp, DT_DIR) ? T_YES : T_NO;
is_dir = DT_MUST_BE (dp, DT_DIR);
else
{
struct stat sbuf;
if (fstatat (fd_cwd, filename, &sbuf, AT_SYMLINK_NOFOLLOW))
if (fstatat (fd_cwd, filename, st, AT_SYMLINK_NOFOLLOW))
{
if (errno == ENOENT && x->ignore_missing_files)
return RM_OK;
@@ -984,11 +1015,11 @@ remove_entry (int fd_cwd, Dirstack_state const *ds, char const *filename,
return RM_ERROR;
}
is_dir = S_ISDIR (sbuf.st_mode) ? T_YES : T_NO;
is_dir = !! S_ISDIR (st->st_mode);
}
}
if (is_dir == T_NO)
if (! is_dir)
{
/* At this point, barring race conditions, FILENAME is known
to be a non-directory, so it's ok to try to unlink it. */
@@ -1002,7 +1033,7 @@ remove_entry (int fd_cwd, Dirstack_state const *ds, char const *filename,
if (! x->recursive)
{
error (0, EISDIR, _("cannot remove directory %s"),
error (0, EISDIR, _("cannot remove %s"),
quote (full_filename (filename)));
return RM_ERROR;
}
@@ -1130,7 +1161,8 @@ remove_cwd_entries (DIR **dirp,
case can decide whether to use unlink or chdir.
Systems without the d_type member will have to endure
the performance hit of first calling lstat F. */
tmp_status = remove_entry (dirfd (*dirp), ds, f, x, dp);
cache_stat_init (subdir_sb);
tmp_status = remove_entry (dirfd (*dirp), ds, f, subdir_sb, x, dp);
switch (tmp_status)
{
case RM_OK:
@@ -1224,10 +1256,10 @@ remove_cwd_entries (DIR **dirp,
static enum RM_status
remove_dir (int fd_cwd, Dirstack_state *ds, char const *dir,
struct stat *dir_st,
struct rm_options const *x, int *cwd_errno)
{
enum RM_status status;
struct stat dir_sb;
/* There is a race condition in that an attacker could replace the nonempty
directory, DIR, with a symlink between the preceding call to rmdir
@@ -1237,7 +1269,7 @@ remove_dir (int fd_cwd, Dirstack_state *ds, char const *dir,
fd_to_subdirp's fstat, along with the `fstat' and the dev/ino
comparison in AD_push ensure that we detect it and fail. */
DIR *dirp = fd_to_subdirp (fd_cwd, dir, x, 0, &dir_sb, ds, cwd_errno);
DIR *dirp = fd_to_subdirp (fd_cwd, dir, x, 0, dir_st, ds, cwd_errno);
if (dirp == NULL)
{
@@ -1262,14 +1294,14 @@ remove_dir (int fd_cwd, Dirstack_state *ds, char const *dir,
return RM_ERROR;
}
if (ROOT_DEV_INO_CHECK (x->root_dev_ino, &dir_sb))
if (ROOT_DEV_INO_CHECK (x->root_dev_ino, dir_st))
{
ROOT_DEV_INO_WARN (full_filename (dir));
status = RM_ERROR;
goto closedir_and_return;
}
AD_push (dirfd (dirp), ds, dir, &dir_sb);
AD_push (dirfd (dirp), ds, dir, dir_st);
AD_INIT_OTHER_MEMBERS ();
status = RM_OK;
@@ -1314,10 +1346,10 @@ remove_dir (int fd_cwd, Dirstack_state *ds, char const *dir,
prompts the user. E.g., we already know that D is a directory
and that it's almost certainly empty, yet we lstat it.
But that's no big deal since we're interactive. */
Ternary is_dir;
struct stat empty_st; cache_stat_init (&empty_st);
Ternary is_empty;
enum RM_status s = prompt (fd, ds, empty_dir, x,
PA_REMOVE_DIR, &is_dir, &is_empty);
enum RM_status s = prompt (fd, ds, empty_dir, &empty_st, x,
PA_REMOVE_DIR, &is_empty);
if (s != RM_OK)
{
@@ -1371,18 +1403,39 @@ static enum RM_status
rm_1 (Dirstack_state *ds, char const *filename,
struct rm_options const *x, int *cwd_errno)
{
struct stat st;
cache_stat_init (&st);
char const *base = last_component (filename);
if (dot_or_dotdot (base))
{
error (0, 0, _("cannot remove `.' or `..'"));
error (0, 0, _(base == filename
? "cannot remove directory %s"
: "cannot remove %s directory %s"),
quote_n (0, base), quote_n (1, filename));
return RM_ERROR;
}
if (x->root_dev_ino)
{
if (cache_fstatat (AT_FDCWD, filename, &st, AT_SYMLINK_NOFOLLOW) != 0)
{
if (errno == ENOENT && x->ignore_missing_files)
return RM_OK;
error (0, errno, _("cannot remove %s"), quote (filename));
return RM_ERROR;
}
if (SAME_INODE (st, *(x->root_dev_ino)))
{
error (0, 0, _("cannot remove root directory %s"), quote (filename));
return RM_ERROR;
}
}
AD_push_initial (ds);
AD_INIT_OTHER_MEMBERS ();
int fd_cwd = AT_FDCWD;
enum RM_status status = remove_entry (fd_cwd, ds, filename, x, NULL);
enum RM_status status = remove_entry (fd_cwd, ds, filename, &st, x, NULL);
if (status == RM_NONEMPTY_DIR)
{
/* In the event that remove_dir->remove_cwd_entries detects
@@ -1391,7 +1444,7 @@ rm_1 (Dirstack_state *ds, char const *filename,
if (setjmp (ds->current_arg_jumpbuf))
status = RM_ERROR;
else
status = remove_dir (fd_cwd, ds, filename, x, cwd_errno);
status = remove_dir (fd_cwd, ds, filename, &st, x, cwd_errno);
AD_stack_clear (ds);
}