On 25/09/2022 00:37, Paul Eggert wrote:
I ran into this problem when attempting to recursively
remove a directory in a filesystem on flaky hardware.
Although the underlying readdir syscall failed with errno == EIO,
rm issued no diagnostic about the I/O error.
This looks good.
How about the attached to add a NEWS entry,
and add DS_EMPTY, DS_NONEMPTY enums to make the code easier to read?
thanks,
Pádraig
diff --git a/NEWS b/NEWS
index d1c400e67..7f4eab921 100644
--- a/NEWS
+++ b/NEWS
@@ -60,6 +60,10 @@ GNU coreutils NEWS -*- outline -*-
date --debug now diagnoses if multiple --date or --set options are
specified, as only the last specified is significant in that case.
+ rm outputs more accurate diagnostics in the presence of errors
+ when removing directories. For example EIO will be faithfully
+ diagnosed, rather than being conflated with ENOTEMPTY.
+
* Noteworthy changes in release 9.1 (2022-04-15) [stable]
diff --git a/src/remove.c b/src/remove.c
index 0b6754bf7..a19794d5e 100644
--- a/src/remove.c
+++ b/src/remove.c
@@ -164,11 +164,11 @@ write_protected_non_symlink (int fd_cwd,
This is -1 if the directory is empty, 0 if it is nonempty,
and a positive error number if there was trouble determining the status,
e.g., it is not a directory, or permissions problems, or I/O errors.
- Use *DIR_STATUS is a cache for the status. */
+ Use *DIR_STATUS as a cache for the status. */
static int
get_dir_status (FTS const *fts, FTSENT const *ent, int *dir_status)
{
- if (*dir_status < -1)
+ if (*dir_status == DS_UNKNOWN)
*dir_status = directory_status (fts->fts_cwd_fd, ent->fts_accpath);
return *dir_status;
}
@@ -254,7 +254,7 @@ prompt (FTS const *fts, FTSENT const *ent, bool is_dir,
prompting the user */
if ( ! (x->recursive
|| (x->remove_empty_directories
- && get_dir_status (fts, ent, dir_status) < 0)))
+ && get_dir_status (fts, ent, dir_status) == DS_EMPTY)))
{
write_protected = -1;
wp_errno = *dir_status <= 0 ? EISDIR : *dir_status;
@@ -273,7 +273,7 @@ prompt (FTS const *fts, FTSENT const *ent, bool is_dir,
/* Issue the prompt. */
if (dirent_type == DT_DIR
&& mode == PA_DESCEND_INTO_DIR
- && get_dir_status (fts, ent, dir_status) == 0)
+ && get_dir_status (fts, ent, dir_status) == DS_NONEMPTY)
fprintf (stderr,
(write_protected
? _("%s: descend into write-protected directory %s? ")
@@ -427,14 +427,14 @@ excise (FTS *fts, FTSENT *ent, struct rm_options const *x, bool is_dir)
static enum RM_status
rm_fts (FTS *fts, FTSENT *ent, struct rm_options const *x)
{
- int dir_status = -2;
+ int dir_status = DS_UNKNOWN;
switch (ent->fts_info)
{
case FTS_D: /* preorder directory */
if (! x->recursive
&& !(x->remove_empty_directories
- && get_dir_status (fts, ent, &dir_status) < 0))
+ && get_dir_status (fts, ent, &dir_status) == DS_EMPTY))
{
/* This is the first (pre-order) encounter with a directory
that we cannot delete.
@@ -512,7 +512,7 @@ rm_fts (FTS *fts, FTSENT *ent, struct rm_options const *x)
enum RM_status s = prompt (fts, ent, true /*is_dir*/, x,
PA_DESCEND_INTO_DIR, &dir_status);
- if (s == RM_USER_ACCEPTED && dir_status == -1)
+ if (s == RM_USER_ACCEPTED && dir_status == DS_EMPTY)
{
/* When we know (from prompt when in interactive mode)
that this is an empty directory, don't prompt twice. */
diff --git a/src/system.h b/src/system.h
index 91228ec13..c63b66741 100644
--- a/src/system.h
+++ b/src/system.h
@@ -291,6 +291,11 @@ readdir_ignoring_dot_and_dotdot (DIR *dirp)
0 if DIR is a nonempty directory,
and a positive error number if there was trouble determining
whether DIR is an empty or nonempty directory. */
+enum {
+ DS_UNKNOWN = -2,
+ DS_EMPTY = -1,
+ DS_NONEMPTY = 0,
+};
static inline int
directory_status (int fd_cwd, char const *dir)
{
@@ -316,7 +321,7 @@ directory_status (int fd_cwd, char const *dir)
no_direntries = !readdir_ignoring_dot_and_dotdot (dirp);
saved_errno = errno;
closedir (dirp);
- return no_direntries && saved_errno == 0 ? -1 : saved_errno;
+ return no_direntries && saved_errno == 0 ? DS_EMPTY : saved_errno;
}
/* Factor out some of the common --help and --version processing code. */