📄 remove.c
字号:
error (0, errno, _("cannot remove %s"),
quote (full_filename (filename)));
return RM_ERROR;
}
if (! x->recursive)
{
error (0, EISDIR, _("cannot remove directory %s"),
quote (full_filename (filename)));
return RM_ERROR;
}
if (is_empty_directory == T_YES)
{
DO_RMDIR (filename, x);
/* Don't diagnose any failure here.
It'll be detected when the caller tries another way. */
}
#else
if (is_dir == T_YES && ! x->recursive)
{
error (0, EISDIR, _("cannot remove directory %s"),
quote (full_filename (filename)));
return RM_ERROR;
}
/* is_empty_directory is set iff it's ok to use rmdir.
Note that it's set only in interactive mode -- in which case it's
an optimization that arranges so that the user is asked just
once whether to remove the directory. */
if (is_empty_directory == T_YES)
DO_RMDIR (filename, x);
/* If we happen to know that FILENAME is a directory, return now
and let the caller remove it -- this saves the overhead of a failed
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_IS_DIR (dp)) || is_dir == T_YES)
return RM_NONEMPTY_DIR;
DO_UNLINK (filename, x);
/* Accept either EISDIR or EPERM as an indication that FILENAME may be
a directory. POSIX says that unlink must set errno to EPERM when it
fails to remove a directory, while Linux-2.4.18 sets it to EISDIR. */
if ((errno != EISDIR && errno != EPERM) || ! x->recursive)
{
/* some other error code. Report it and fail.
Likewise, if we're trying to remove a directory without
the --recursive option. */
int err = errno;
error (0, err, _("cannot remove %s"),
quote (full_filename (filename)));
return RM_ERROR;
}
#endif
return RM_NONEMPTY_DIR;
}
/* Remove entries in `.', the current working directory (cwd).
Upon finding a directory that is both non-empty and that can be chdir'd
into, return zero and set *SUBDIR and fill in SUBDIR_SB, where
SUBDIR is the malloc'd name of the subdirectory if the chdir succeeded,
NULL otherwise (e.g., if opendir failed or if there was no subdirectory).
Likewise, SUBDIR_SB is the result of calling lstat on SUBDIR.
Return RM_OK if all entries are removed. Remove RM_ERROR if any
entry cannot be removed. Otherwise, return RM_USER_DECLINED if
the user declines to remove at least one entry. Remove as much as
possible, continuing even if we fail to remove some entries. */
static enum RM_status
remove_cwd_entries (char **subdir, struct stat *subdir_sb,
struct rm_options const *x)
{
DIR *dirp = opendir (".");
struct AD_ent *top = AD_stack_top ();
enum RM_status status = top->status;
assert (VALID_STATUS (status));
*subdir = NULL;
if (dirp == NULL)
{
if (errno != ENOENT || !x->ignore_missing_files)
{
error (0, errno, _("cannot open directory %s"),
quote (full_filename (".")));
return RM_ERROR;
}
}
while (1)
{
struct dirent *dp;
enum RM_status tmp_status;
const char *f;
/* Set errno to zero so we can distinguish between a readdir failure
and when readdir simply finds that there are no more entries. */
errno = 0;
if ((dp = readdir (dirp)) == NULL)
{
if (errno)
{
/* Save/restore errno across closedir call. */
int e = errno;
closedir (dirp);
errno = e;
/* Arrange to give a diagnostic after exiting this loop. */
dirp = NULL;
}
break;
}
f = dp->d_name;
if (DOT_OR_DOTDOT (f))
continue;
/* Skip files we've already tried/failed to remove. */
if ( ! AD_is_removable (f))
continue;
/* Pass dp->d_type info to remove_entry so the non-glibc
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 (f, x, dp);
switch (tmp_status)
{
case RM_OK:
/* do nothing */
break;
case RM_ERROR:
case RM_USER_DECLINED:
AD_mark_as_unremovable (f);
UPDATE_STATUS (status, tmp_status);
break;
case RM_NONEMPTY_DIR:
/* Record dev/ino of F so that we can compare
that with dev/ino of `.' after the chdir.
This dev/ino pair is also used in cycle detection. */
if (lstat (f, subdir_sb))
error (EXIT_FAILURE, errno, _("cannot lstat %s"),
quote (full_filename (f)));
if (chdir (f))
{
error (0, errno, _("cannot chdir from %s to %s"),
quote_n (0, full_filename (".")), quote_n (1, f));
AD_mark_as_unremovable (f);
status = RM_ERROR;
break;
}
cycle_check (subdir_sb);
*subdir = xstrdup (f);
break;
}
/* Record status for this directory. */
UPDATE_STATUS (top->status, status);
if (*subdir)
break;
}
if (dirp == NULL || CLOSEDIR (dirp) != 0)
{
/* Note that this diagnostic serves for both readdir
and closedir failures. */
error (0, errno, _("reading directory %s"), quote (full_filename (".")));
status = RM_ERROR;
}
return status;
}
/* Remove the hierarchy rooted at DIR.
Do that by changing into DIR, then removing its contents, then
returning to the original working directory and removing DIR itself.
Don't use recursion. Be careful when using chdir ".." that we
return to the same directory from which we came, if necessary.
Return 1 for success, 0 if some file cannot be removed or if
a chdir fails.
If the working directory cannot be restored, exit immediately. */
static enum RM_status
remove_dir (char const *dir, struct saved_cwd **cwd_state,
struct rm_options const *x)
{
enum RM_status status;
struct stat dir_sb;
/* Save any errno (from caller's failed remove_entry call), in case DIR
is not a directory, so that we can give a reasonable diagnostic. */
int saved_errno = errno;
if (*cwd_state == NULL)
{
*cwd_state = XMALLOC (struct saved_cwd, 1);
if (save_cwd (*cwd_state))
return RM_ERROR;
AD_push_initial (*cwd_state);
}
/* There is a race condition in that an attacker could replace the nonempty
directory, DIR, with a symlink between the preceding call to rmdir
(in our caller) and the chdir below. However, the following lstat,
along with the `stat (".",...' and dev/ino comparison in AD_push
ensure that we detect it and fail. */
if (lstat (dir, &dir_sb))
{
error (0, errno,
_("cannot lstat %s"), quote (full_filename (dir)));
return RM_ERROR;
}
if (chdir (dir))
{
if (! S_ISDIR (dir_sb.st_mode))
{
/* This happens on Linux-2.4.18 when a non-privileged user tries
to delete a file that is owned by another user in a directory
like /tmp that has the S_ISVTX flag set. */
assert (saved_errno == EPERM);
error (0, saved_errno,
_("cannot remove %s"), quote (full_filename (dir)));
}
else
{
error (0, errno,
_("cannot chdir from %s to %s"),
quote_n (0, full_filename (".")), quote_n (1, dir));
}
return RM_ERROR;
}
AD_push (dir, &dir_sb);
status = RM_OK;
while (1)
{
char *subdir = NULL;
struct stat subdir_sb;
enum RM_status tmp_status = remove_cwd_entries (&subdir, &subdir_sb, x);
if (tmp_status != RM_OK)
{
UPDATE_STATUS (status, tmp_status);
AD_mark_current_as_unremovable ();
}
if (subdir)
{
AD_push (subdir, &subdir_sb);
free (subdir);
continue;
}
/* Execution reaches this point when we've removed the last
removable entry from the current directory. */
{
char *d = AD_pop_and_chdir ();
/* Try to remove D only if remove_cwd_entries succeeded. */
if (tmp_status == RM_OK)
{
/* This does a little more work than necessary when it actually
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;
Ternary is_empty;
enum RM_status s = prompt (d, x, PA_REMOVE_DIR, &is_dir, &is_empty);
if (s != RM_OK)
{
free (d);
return s;
}
if (rmdir (d) == 0)
{
if (x->verbose)
printf (_("removed directory: %s\n"),
quote (full_filename (d)));
}
else
{
error (0, errno, _("cannot remove directory %s"),
quote (full_filename (d)));
AD_mark_as_unremovable (d);
status = RM_ERROR;
UPDATE_STATUS (AD_stack_top()->status, status);
}
}
free (d);
if (AD_stack_height () == 1)
break;
}
}
return status;
}
/* Remove the file or directory specified by FILENAME.
Return RM_OK if it is removed, and RM_ERROR or RM_USER_DECLINED if not.
On input, the first time this function is called, CWD_STATE should be
the address of a NULL pointer. Do not modify it for any subsequent calls.
On output, it is either that same NULL pointer or the address of
a malloc'd `struct saved_cwd' that may be freed. */
static enum RM_status
rm_1 (char const *filename,
struct rm_options const *x, struct saved_cwd **cwd_state)
{
char *base = base_name (filename);
enum RM_status status;
if (DOT_OR_DOTDOT (base))
{
error (0, 0, _("cannot remove `.' or `..'"));
return RM_ERROR;
}
status = remove_entry (filename, x, NULL);
if (status != RM_NONEMPTY_DIR)
return status;
return remove_dir (filename, cwd_state, x);
}
/* Remove all files and/or directories specified by N_FILES and FILE.
Apply the options in X. */
enum RM_status
rm (size_t n_files, char const *const *file, struct rm_options const *x)
{
struct saved_cwd *cwd_state = NULL;
enum RM_status status = RM_OK;
size_t i;
obstack_init (&dir_stack);
obstack_init (&len_stack);
obstack_init (&Active_dir);
for (i = 0; i < n_files; i++)
{
enum RM_status s = rm_1 (file[i], x, &cwd_state);
assert (VALID_STATUS (s));
UPDATE_STATUS (status, s);
}
obstack_free (&dir_stack, NULL);
obstack_free (&len_stack, NULL);
obstack_free (&Active_dir, NULL);
XFREE (cwd_state);
return status;
}
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -