summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorPaul Eggert <eggert@cs.ucla.edu>2006-09-03 02:54:51 +0000
committerPaul Eggert <eggert@cs.ucla.edu>2006-09-03 02:54:51 +0000
commite5fc4f24aae41c3028c1dff46f00a103db5bfd44 (patch)
tree505dc72dfeed0aae7eb5d76cb22d7bca817f0bb8 /src
parentfcf498b1b4e8b87b5f964bd1b68d2157b1199330 (diff)
downloadcoreutils-e5fc4f24aae41c3028c1dff46f00a103db5bfd44.tar.xz
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 ../.
Diffstat (limited to 'src')
-rw-r--r--src/remove.c163
1 files changed, 108 insertions, 55 deletions
diff --git a/src/remove.c b/src/remove.c
index 368375864..cda1d30d4 100644
--- a/src/remove.c
+++ b/src/remove.c
@@ -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);
}