From 9de1d153f82243aeaaf19b0e5da2345f9b8652e3 Mon Sep 17 00:00:00 2001 From: =?utf8?q?P=C3=A1draig=20Brady?=
Date: Tue, 16 Feb 2021 05:07:40 +0000
Subject: [PATCH] rmdir: diagnose non following of symlinks with trailing slash
GNU/Linux is unusual here in that rmdir("symlink/") returns ENOTDIR,
whereas Solaris and FreeBSD at least, will follow the symlink
and remove the target directory. We don't make the behavior
on Linux kernels consistent, but at least clarify
the confusing error message.
* src/rmdir (main): Output a specific error message for the above case.
(remove_parents): In the error message, don't assume intermediate paths
are directories, as they could be symlinks.
* tests/rmdir/symlink-errors.sh: Add a new test.
* tests/local.mk: Reference the new test.
* NEWS: Mention the improvement.
---
NEWS | 3 ++
src/rmdir.c | 55 +++++++++++++++++++++++++----
tests/local.mk | 1 +
tests/rmdir/symlink-errors.sh | 66 +++++++++++++++++++++++++++++++++++
4 files changed, 119 insertions(+), 6 deletions(-)
create mode 100755 tests/rmdir/symlink-errors.sh
diff --git a/NEWS b/NEWS
index 08a58d56a6..aad05df6db 100644
--- a/NEWS
+++ b/NEWS
@@ -75,6 +75,9 @@ GNU coreutils NEWS -*- outline -*-
df now recognizes these file systems as remote:
acfs, coda, fhgfs, gpfs, ibrix, ocfs2, and vxfs.
+ rmdir now clarifies the error if a symlink_to_dir/ has not been traversed.
+ This is the case on GNU/Linux systems, where the trailing slash is ignored.
+
stat and tail now know about the "devmem", "exfat", "vboxsf", and "zonefs"
file system types. stat -f -c%T now reports the file system type,
and tail -f uses polling for "vboxsf" and inotify for the others.
diff --git a/src/rmdir.c b/src/rmdir.c
index dffe24bc74..3a9911ff06 100644
--- a/src/rmdir.c
+++ b/src/rmdir.c
@@ -144,9 +144,19 @@ remove_parents (char *dir)
}
else
{
- /* Barring race conditions, DIR is expected to be a directory. */
- error (0, rmdir_errno, _("failed to remove directory %s"),
- quoteaf (dir));
+ char const* error_msg;
+ if (rmdir_errno != ENOTDIR)
+ {
+ /* Barring race conditions,
+ DIR is expected to be a directory. */
+ error_msg = N_("failed to remove directory %s");
+ }
+ else
+ {
+ /* A path component could be a symbolic link */
+ error_msg = N_("failed to remove %s");
+ }
+ error (0, rmdir_errno, _(error_msg), quoteaf (dir));
}
break;
}
@@ -238,9 +248,42 @@ main (int argc, char **argv)
if (ignorable_failure (rmdir_errno, dir))
continue;
- /* Here, the diagnostic is less precise, since we have no idea
- whether DIR is a directory. */
- error (0, rmdir_errno, _("failed to remove %s"), quoteaf (dir));
+ /* Distinguish the case for a symlink with trailing slash.
+ On Linux, rmdir(2) confusingly does not follow the symlink,
+ thus giving the errno ENOTDIR, while on other systems the symlink
+ is followed. We don't provide consistent behavior here,
+ but at least we provide a more accurate error message. */
+ bool custom_error = false;
+ if (rmdir_errno == ENOTDIR)
+ {
+ char const *last_unix_slash = strrchr (dir, '/');
+ if (last_unix_slash && (*(last_unix_slash + 1) == '\0'))
+ {
+ struct stat st;
+ int ret = stat (dir, &st);
+ /* Some other issue following, or is actually a directory. */
+ if ((ret != 0 && errno != ENOTDIR) || S_ISDIR (st.st_mode))
+ {
+ /* Ensure the last component was a symlink. */
+ char* dir_arg = xstrdup (dir);
+ strip_trailing_slashes (dir);
+ ret = lstat (dir, &st);
+ if (ret == 0 && S_ISLNK (st.st_mode))
+ {
+ error (0, 0,
+ _("failed to remove %s:"
+ " Symbolic link not followed"),
+ quoteaf (dir_arg));
+ custom_error = true;
+ }
+ free (dir_arg);
+ }
+ }
+ }
+
+ if (! custom_error)
+ error (0, rmdir_errno, _("failed to remove %s"), quoteaf (dir));
+
ok = false;
}
else if (remove_empty_parents)
diff --git a/tests/local.mk b/tests/local.mk
index a6d4145816..27e31ec8e4 100644
--- a/tests/local.mk
+++ b/tests/local.mk
@@ -695,6 +695,7 @@ all_tests = \
tests/readlink/rl-1.sh \
tests/rmdir/fail-perm.sh \
tests/rmdir/ignore.sh \
+ tests/rmdir/symlink-errors.sh \
tests/rmdir/t-slash.sh \
tests/tail-2/assert-2.sh \
tests/tail-2/big-4gb.sh \
diff --git a/tests/rmdir/symlink-errors.sh b/tests/rmdir/symlink-errors.sh
new file mode 100755
index 0000000000..911835095e
--- /dev/null
+++ b/tests/rmdir/symlink-errors.sh
@@ -0,0 +1,66 @@
+#!/bin/sh
+# make sure rmdir outputs clear errors in the presence of symlinks
+
+# Copyright (C) 2021 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