From: Colin Watson Date: Tue, 11 Feb 2020 10:45:46 +0000 (+0000) Subject: ls: issue error message on removed directory X-Git-Tag: v8.32~14 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=05a99f7d7f8e0999994b760bb6337ca10ea0a14b;p=thirdparty%2Fcoreutils.git 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. --- diff --git a/NEWS b/NEWS index a03572d014..841f3f854e 100644 --- a/NEWS +++ b/NEWS @@ -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. diff --git a/src/ls.c b/src/ls.c index 4acf5f44d9..24b983287d 100644 --- a/src/ls.c +++ b/src/ls.c @@ -49,6 +49,10 @@ # include #endif +#ifdef __linux__ +# include +#endif + #include #include #include @@ -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; diff --git a/tests/local.mk b/tests/local.mk index 0aabdaacc6..7c8196a970 100644 --- a/tests/local.mk +++ b/tests/local.mk @@ -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 \ diff --git a/tests/ls/removed-directory.sh b/tests/ls/removed-directory.sh new file mode 100755 index 0000000000..e8c835dabf --- /dev/null +++ b/tests/ls/removed-directory.sh @@ -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 . + +. "${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