>From af86d4cf3aa5c7274fbf8628982e25e10d9009df Mon Sep 17 00:00:00 2001 From: =?UTF-8?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
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 behviour
on Linux 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 79bb25e86..edd17eba3 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", "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 dffe24bc7..3a9911ff0 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 a6d414581..27e31ec8e 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 000000000..911835095
--- /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