]> git.ipfire.org Git - thirdparty/coreutils.git/commitdiff
ls: issue error message on removed directory
authorColin Watson <cjwatson@debian.org>
Tue, 11 Feb 2020 10:45:46 +0000 (10:45 +0000)
committerPádraig Brady <P@draigBrady.com>
Thu, 27 Feb 2020 14:11:37 +0000 (14:11 +0000)
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.

NEWS
src/ls.c
tests/local.mk
tests/ls/removed-directory.sh [new file with mode: 0755]

diff --git a/NEWS b/NEWS
index a03572d0141f4bb07c8682ef4475518e90c9e686..841f3f854e82ace630e3a159ac6e6f1ba4703180 100644 (file)
--- 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.
 
index 4acf5f44d96925d278d19dc495146bc8e326bb65..24b983287dfcfbc5b68ad634f825066c8a93d440 100644 (file)
--- a/src/ls.c
+++ b/src/ls.c
 # 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;
 
index 0aabdaacc6148ac3af1a2a3ee082a852e343b74c..7c8196a970e58d4b7fc2f89bff0ad30c1469a423 100644 (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                                \
diff --git a/tests/ls/removed-directory.sh b/tests/ls/removed-directory.sh
new file mode 100755 (executable)
index 0000000..e8c835d
--- /dev/null
@@ -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