ls: issue error message on removed directory

If the current directory has been removed, then "ls" confusingly
produced no output and no error message, indistinguishable from
running on an empty directory.

* src/ls.c (print_dir): Report ENOENT on GNU/Linux if readdir
finds no directory entries at all, not even "." or "..",
and a recheck with the getdents syscall returns ENOENT.
We recheck with getdents() as POSIX states that
"The directory entries for dot and dot-dot are optional".
* tests/ls/removed-directory.sh: New file.
* tests/local.mk (all_tests): Add new test.
* NEWS: Mention the change in behavior.
Reported by Owen Thomas.
This commit is contained in:
Colin Watson 2020-02-11 10:45:46 +00:00 committed by Pádraig Brady
parent 93db70867d
commit 05a99f7d7f
4 changed files with 72 additions and 0 deletions

4
NEWS
View File

@ -65,6 +65,10 @@ GNU coreutils NEWS -*- outline -*-
[The old behavior was introduced in sh-utils 2.0.15 ca. 1999, predating
coreutils package.]
ls issues an error message on a removed directory, on GNU/Linux systems.
Previously no error and no entries were output, and so indistinguishable
from an empty directory, with default ls options.
uniq no longer uses strcoll() to determine string equivalence,
and so will operate more efficiently and consistently.

View File

@ -49,6 +49,10 @@
# include <sys/ptem.h>
#endif
#ifdef __linux__
# include <sys/syscall.h>
#endif
#include <stdio.h>
#include <assert.h>
#include <setjmp.h>
@ -2892,6 +2896,7 @@ print_dir (char const *name, char const *realname, bool command_line_arg)
struct dirent *next;
uintmax_t total_blocks = 0;
static bool first = true;
bool found_any_entries = false;
errno = 0;
dirp = opendir (name);
@ -2967,6 +2972,7 @@ print_dir (char const *name, char const *realname, bool command_line_arg)
next = readdir (dirp);
if (next)
{
found_any_entries = true;
if (! file_ignored (next->d_name))
{
enum filetype type = unknown;
@ -3012,6 +3018,22 @@ print_dir (char const *name, char const *realname, bool command_line_arg)
if (errno != EOVERFLOW)
break;
}
#ifdef __linux__
else if (! found_any_entries)
{
/* If readdir finds no directory entries at all, not even "." or
"..", then double check that the directory exists. */
if (syscall (SYS_getdents, dirfd (dirp), NULL, 0) == -1
&& errno != EINVAL)
{
/* We exclude EINVAL as that pertains to buffer handling,
and we've passed NULL as the buffer for simplicity.
ENOENT is returned if appropriate before buffer handling. */
file_failure (command_line_arg, _("reading directory %s"), name);
}
break;
}
#endif
else
break;

View File

@ -615,6 +615,7 @@ all_tests = \
tests/ls/quote-align.sh \
tests/ls/readdir-mountpoint-inode.sh \
tests/ls/recursive.sh \
tests/ls/removed-directory.sh \
tests/ls/root-rel-symlink-color.sh \
tests/ls/rt-1.sh \
tests/ls/slink-acl.sh \

45
tests/ls/removed-directory.sh Executable file
View File

@ -0,0 +1,45 @@
#!/bin/sh
# If ls is asked to list a removed directory (e.g. the parent process's
# current working directory that has been removed by another process), it
# emits an error message.
# Copyright (C) 2020 Free Software Foundation, Inc.
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src
print_ver_ ls
case $host_triplet in
*linux*) ;;
*) skip_ 'non linux kernel' ;;
esac
LS_FAILURE=2
cat <<\EOF >exp-err || framework_failure_
ls: reading directory '.': No such file or directory
EOF
cwd=$(pwd)
mkdir d || framework_failure_
cd d || framework_failure_
rmdir ../d || framework_failure_
returns_ $LS_FAILURE ls >../out 2>../err || fail=1
cd "$cwd" || framework_failure_
compare /dev/null out || fail=1
compare exp-err err || fail=1
Exit $fail