summaryrefslogtreecommitdiffstats
path: root/tests/rm
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 17:39:29 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 17:39:29 +0000
commit8ffec2a3aba6f114784e11f89ef1d57a096ae540 (patch)
treeccebcbad06203e8241a8e7249f8e6c478a3682ea /tests/rm
parentInitial commit. (diff)
downloadcoreutils-8ffec2a3aba6f114784e11f89ef1d57a096ae540.tar.xz
coreutils-8ffec2a3aba6f114784e11f89ef1d57a096ae540.zip
Adding upstream version 8.32.upstream/8.32upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'tests/rm')
-rwxr-xr-xtests/rm/cycle.sh36
-rwxr-xr-xtests/rm/d-1.sh38
-rwxr-xr-xtests/rm/d-2.sh37
-rwxr-xr-xtests/rm/d-3.sh37
-rwxr-xr-xtests/rm/dangling-symlink.sh46
-rwxr-xr-xtests/rm/deep-1.sh53
-rwxr-xr-xtests/rm/deep-2.sh53
-rwxr-xr-xtests/rm/dir-no-w.sh42
-rwxr-xr-xtests/rm/dir-nonrecur.sh34
-rwxr-xr-xtests/rm/dot-rel.sh29
-rwxr-xr-xtests/rm/empty-inacc.sh38
-rwxr-xr-xtests/rm/empty-name.pl60
-rwxr-xr-xtests/rm/ext3-perf.sh83
-rwxr-xr-xtests/rm/f-1.sh26
-rwxr-xr-xtests/rm/fail-2eperm.sh57
-rwxr-xr-xtests/rm/fail-eacces.sh54
-rwxr-xr-xtests/rm/fail-eperm.xpl150
-rwxr-xr-xtests/rm/hash.sh37
-rwxr-xr-xtests/rm/i-1.sh40
-rwxr-xr-xtests/rm/i-never.sh33
-rwxr-xr-xtests/rm/i-no-r.sh34
-rwxr-xr-xtests/rm/ignorable.sh30
-rwxr-xr-xtests/rm/inaccessible.sh49
-rwxr-xr-xtests/rm/interactive-always.sh87
-rwxr-xr-xtests/rm/interactive-once.sh148
-rwxr-xr-xtests/rm/ir-1.sh56
-rwxr-xr-xtests/rm/isatty.sh55
-rwxr-xr-xtests/rm/many-dir-entries-vs-OOM.sh47
-rwxr-xr-xtests/rm/no-give-up.sh42
-rwxr-xr-xtests/rm/one-file-system.sh51
-rwxr-xr-xtests/rm/one-file-system2.sh27
-rwxr-xr-xtests/rm/r-1.sh40
-rwxr-xr-xtests/rm/r-2.sh45
-rwxr-xr-xtests/rm/r-3.sh48
-rwxr-xr-xtests/rm/r-4.sh51
-rwxr-xr-xtests/rm/r-root.sh320
-rwxr-xr-xtests/rm/read-only.sh52
-rwxr-xr-xtests/rm/readdir-bug.sh41
-rwxr-xr-xtests/rm/rm-readdir-fail.sh120
-rwxr-xr-xtests/rm/rm1.sh48
-rwxr-xr-xtests/rm/rm2.sh55
-rwxr-xr-xtests/rm/rm3.sh73
-rwxr-xr-xtests/rm/rm4.sh31
-rwxr-xr-xtests/rm/rm5.sh52
-rwxr-xr-xtests/rm/sunos-1.sh29
-rwxr-xr-xtests/rm/unread2.sh35
-rwxr-xr-xtests/rm/unread3.sh43
-rwxr-xr-xtests/rm/unreadable.pl50
-rwxr-xr-xtests/rm/v-slash.sh34
49 files changed, 2776 insertions, 0 deletions
diff --git a/tests/rm/cycle.sh b/tests/rm/cycle.sh
new file mode 100755
index 0000000..82bdd1e
--- /dev/null
+++ b/tests/rm/cycle.sh
@@ -0,0 +1,36 @@
+#!/bin/sh
+# rm (coreutils-4.5.4) could be tricked into mistakenly reporting a cycle.
+
+# Copyright (C) 2003-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_ rm
+skip_if_root_
+
+mkdir -p a/b
+touch a/b/file
+chmod ug-w a/b
+
+
+rm -rf a a 2>&1 | sed 's/:[^:]*$//' > out || fail=1
+cat <<\EOF > exp
+rm: cannot remove 'a/b/file'
+rm: cannot remove 'a/b/file'
+EOF
+
+compare exp out || fail=1
+
+Exit $fail
diff --git a/tests/rm/d-1.sh b/tests/rm/d-1.sh
new file mode 100755
index 0000000..1195522
--- /dev/null
+++ b/tests/rm/d-1.sh
@@ -0,0 +1,38 @@
+#!/bin/sh
+# Test "rm --dir --verbose".
+
+# Copyright (C) 2012-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_ rm
+
+mkdir a || framework_failure_
+> b || framework_failure_
+
+rm --verbose --dir a b > out || fail=1
+
+cat <<\EOF > exp || framework_failure_
+removed directory 'a'
+removed 'b'
+EOF
+
+test -e a && fail=1
+test -e b && fail=1
+
+# Compare expected and actual output.
+compare exp out || fail=1
+
+Exit $fail
diff --git a/tests/rm/d-2.sh b/tests/rm/d-2.sh
new file mode 100755
index 0000000..7ca65a1
--- /dev/null
+++ b/tests/rm/d-2.sh
@@ -0,0 +1,37 @@
+#!/bin/sh
+# Ensure that 'rm -d dir' (i.e., without --recursive) gives a reasonable
+# diagnostic when failing.
+
+# Copyright (C) 2012-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_ rm
+
+mkdir d || framework_failure_
+> d/a || framework_failure_
+
+rm -d d 2> out && fail=1
+
+# Accept any of these: EEXIST, ENOTEMPTY
+sed 's/: File exists/: Directory not empty/' out > out2
+
+printf "%s\n" \
+ "rm: cannot remove 'd': Directory not empty" \
+ > exp || framework_failure_
+
+compare exp out2 || fail=1
+
+Exit $fail
diff --git a/tests/rm/d-3.sh b/tests/rm/d-3.sh
new file mode 100755
index 0000000..df5838d
--- /dev/null
+++ b/tests/rm/d-3.sh
@@ -0,0 +1,37 @@
+#!/bin/sh
+# Ensure that 'rm -d -i dir' (i.e., without --recursive) gives a prompt and
+# then deletes the directory if it is empty
+
+# Copyright (C) 2012-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_ rm
+
+mkdir d || framework_failure_
+
+echo "y" | rm -i -d --verbose d > out 2> out.err || fail=1
+printf "%s" \
+ "rm: remove directory 'd'? " \
+ > exp.err || framework_failure_
+
+printf "%s\n" \
+ "removed directory 'd'" \
+ > exp || framework_failure_
+
+compare exp out || fail=1
+compare exp.err out.err || fail=1
+
+Exit $fail
diff --git a/tests/rm/dangling-symlink.sh b/tests/rm/dangling-symlink.sh
new file mode 100755
index 0000000..bd211aa
--- /dev/null
+++ b/tests/rm/dangling-symlink.sh
@@ -0,0 +1,46 @@
+#!/bin/sh
+# rm should not prompt before removing a dangling symlink.
+# Likewise for a non-dangling symlink.
+# But for fileutils-4.1.9, it would do the former and
+# for fileutils-4.1.10 the latter.
+
+# Copyright (C) 2002-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_ rm
+
+ln -s no-file dangle
+ln -s / symlink
+
+# Terminate any background processes
+cleanup_() { kill $pid 2>/dev/null && wait $pid; }
+
+rm ---presume-input-tty dangle symlink & pid=$!
+# The buggy rm (fileutils-4.1.9) would hang here, waiting for input.
+
+# Wait up to 6.3s for rm to remove the files
+check_files_removed() {
+ local present=0
+ sleep $1
+ ls -l dangle > /dev/null 2>&1 && present=1
+ ls -l symlink > /dev/null 2>&1 && present=1
+ test $present = 0
+}
+retry_delay_ check_files_removed .1 6 || fail=1
+
+cleanup_
+
+Exit $fail
diff --git a/tests/rm/deep-1.sh b/tests/rm/deep-1.sh
new file mode 100755
index 0000000..85939cc
--- /dev/null
+++ b/tests/rm/deep-1.sh
@@ -0,0 +1,53 @@
+#!/bin/sh
+# Test "rm" with a deep hierarchy.
+
+# Copyright (C) 1997-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/>.
+
+# This is a bit of a torture test for mkdir -p, too.
+# GNU rm performs *much* better on systems that have a d_type member
+# in the directory structure because then it does only one stat per
+# command line argument.
+
+# If this test takes too long on your system, blame the OS.
+
+. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src
+print_ver_ rm
+
+umask 022
+
+
+k20=/k/k/k/k/k/k/k/k/k/k/k/k/k/k/k/k/k/k/k/k
+k200=$k20$k20$k20$k20$k20$k20$k20$k20$k20$k20
+
+# Be careful not to exceed max file name length (usu 512?).
+# Doing so wouldn't affect GNU mkdir or GNU rm, but any tool that
+# operates on the full pathname (like 'test') would choke.
+k_deep=$k200$k200
+
+t=t
+# Create a directory in $t with lots of 'k' components.
+deep=$t$k_deep
+mkdir -p $deep || fail=1
+
+# Make sure the deep dir was created.
+test -d $deep || fail=1
+
+rm -r $t || fail=1
+
+# Make sure all of $t was deleted.
+test -d $t && fail=1
+
+Exit $fail
diff --git a/tests/rm/deep-2.sh b/tests/rm/deep-2.sh
new file mode 100755
index 0000000..9ca58db
--- /dev/null
+++ b/tests/rm/deep-2.sh
@@ -0,0 +1,53 @@
+#!/bin/sh
+# Ensure rm -r DIR does not prompt for very long full relative names in DIR.
+
+# Copyright (C) 2008-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_ rm
+require_perl_
+
+# Root can run this test, but it always succeeds, since for root, all
+# files are writable, and write_protected_non_symlink never reaches
+# the offending euidaccess_stat call.
+skip_if_root_
+
+# ecryptfs for example uses some of the file name space
+# for encrypting filenames, so we must check dynamically.
+name_max=$(stat -f -c %l .)
+test "$name_max" -ge '200' || skip_ "NAME_MAX=$name_max is not sufficient"
+
+mkdir x || framework_failure_
+cd x || framework_failure_
+
+# Construct a hierarchy containing a relative file with a long name
+$PERL \
+ -e 'my $d = "x" x 200; foreach my $i (1..52)' \
+ -e ' { mkdir ($d, 0700) && chdir $d or die "$!" }' \
+ || framework_failure_
+
+cd .. || framework_failure_
+echo n > no || framework_failure_
+
+rm ---presume-input-tty -r x < no > out || fail=1
+
+# expect empty output
+compare /dev/null out || fail=1
+
+# the directory must have been removed
+test -d x && fail=1
+
+Exit $fail
diff --git a/tests/rm/dir-no-w.sh b/tests/rm/dir-no-w.sh
new file mode 100755
index 0000000..96c80e1
--- /dev/null
+++ b/tests/rm/dir-no-w.sh
@@ -0,0 +1,42 @@
+#!/bin/sh
+# rm (without -r) must give a diagnostic for any directory.
+# It must not prompt, even if that directory is unwritable.
+
+# Copyright (C) 2003-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_ rm
+
+mkdir --mode=0500 unwritable-dir || framework_failure_
+
+
+# For rm from coreutils-5.0.1, this would prompt.
+rm ---presume-input-tty unwritable-dir < /dev/null > out-t 2>&1 && fail=1
+cat <<\EOF > exp || framework_failure_
+rm: cannot remove 'unwritable-dir': Is a directory
+EOF
+
+# When run by a non-privileged user we get this:
+# rm: cannot remove directory 'unwritable-dir': Is a directory
+# When run by root we get this:
+# rm: cannot remove 'unwritable-dir': Is a directory
+# Normalize the message.
+sed 's/remove directory/remove/' out-t > out
+rm -f out-t
+
+compare exp out || fail=1
+
+Exit $fail
diff --git a/tests/rm/dir-nonrecur.sh b/tests/rm/dir-nonrecur.sh
new file mode 100755
index 0000000..3bf590b
--- /dev/null
+++ b/tests/rm/dir-nonrecur.sh
@@ -0,0 +1,34 @@
+#!/bin/sh
+# Ensure that 'rm dir' (i.e., without --recursive) gives a reasonable
+# diagnostic when failing.
+
+# Copyright (C) 2005-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_ rm
+
+mkdir d || framework_failure_
+
+
+rm d 2> out && fail=1
+cat <<\EOF > exp || framework_failure_
+rm: cannot remove 'd': Is a directory
+EOF
+
+# Before coreutils-5.93 this test would fail on Solaris 9 and newer.
+compare exp out || fail=1
+
+Exit $fail
diff --git a/tests/rm/dot-rel.sh b/tests/rm/dot-rel.sh
new file mode 100755
index 0000000..0cd39c6
--- /dev/null
+++ b/tests/rm/dot-rel.sh
@@ -0,0 +1,29 @@
+#!/bin/sh
+# Use rm -r to remove two non-empty dot-relative directories.
+# This would have failed between 2004-10-18 and 2004-10-21.
+
+# Copyright (C) 2004-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_ rm
+
+mkdir a b || framework_failure_
+touch a/f b/f || framework_failure_
+
+
+rm -r a b || fail=1
+
+Exit $fail
diff --git a/tests/rm/empty-inacc.sh b/tests/rm/empty-inacc.sh
new file mode 100755
index 0000000..bcaf7e3
--- /dev/null
+++ b/tests/rm/empty-inacc.sh
@@ -0,0 +1,38 @@
+#!/bin/sh
+# Ensure that rm -rf removes an empty-and-inaccessible directory.
+
+# Copyright (C) 2006-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_ rm
+skip_if_root_
+
+mkdir -m0 inacc || framework_failure_
+
+# Also exercise the different code path that's taken for a directory
+# that is empty (hence removable) and unreadable.
+mkdir -m a-r -p a/unreadable
+
+
+# This would fail for e.g., coreutils-5.93.
+rm -rf inacc || fail=1
+test -d inacc && fail=1
+
+# This would fail for e.g., coreutils-5.97.
+rm -rf a || fail=1
+test -d a && fail=1
+
+Exit $fail
diff --git a/tests/rm/empty-name.pl b/tests/rm/empty-name.pl
new file mode 100755
index 0000000..8aa8765
--- /dev/null
+++ b/tests/rm/empty-name.pl
@@ -0,0 +1,60 @@
+#!/usr/bin/perl
+# Make sure that rm -r '' fails.
+
+# Copyright (C) 1998-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/>.
+
+# On SunOS 4.1.3, running rm -r '' in a nonempty directory may
+# actually remove files with names of entries in the current directory
+# but relative to '/' rather than relative to the current directory.
+
+use strict;
+
+(my $program_name = $0) =~ s|.*/||;
+
+# Turn off localization of executable's output.
+@ENV{qw(LANGUAGE LANG LC_ALL)} = ('C') x 3;
+
+my $prog = 'rm';
+
+# FIXME: copied from misc/ls-misc; factor into Coreutils.pm?
+sub mk_file(@)
+{
+ foreach my $f (@_)
+ {
+ open (F, '>', $f) && close F
+ or die "creating $f: $!\n";
+ }
+}
+
+my @Tests =
+ (
+ # test-name options input expected-output
+ #
+ ['empty-name-1', "''", {EXIT => 1},
+ {ERR => "$prog: cannot remove '': No such file or directory\n"}],
+
+ ['empty-name-2', "a '' b", {EXIT => 1},
+ {ERR => "$prog: cannot remove '': No such file or directory\n"},
+ {PRE => sub { mk_file qw(a b) }},
+ {POST => sub {-f 'a' || -f 'b' and die "a or b remain\n" }},
+ ],
+ );
+
+my $save_temps = $ENV{SAVE_TEMPS};
+my $verbose = $ENV{VERBOSE};
+
+my $fail = run_tests ($program_name, $prog, \@Tests, $save_temps, $verbose);
+exit $fail;
diff --git a/tests/rm/ext3-perf.sh b/tests/rm/ext3-perf.sh
new file mode 100755
index 0000000..6a90f95
--- /dev/null
+++ b/tests/rm/ext3-perf.sh
@@ -0,0 +1,83 @@
+#!/bin/sh
+# ensure that "rm -rf DIR-with-many-entries" is not O(N^2)
+
+# Copyright (C) 2008-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_ rm
+
+very_expensive_
+
+# Using rm -rf to remove a 400k-entry directory takes:
+# - 9 seconds with the patch, on a 2-yr-old system
+# - 350 seconds without the patch, on a high-end system (disk 20-30% faster)
+threshold_seconds=60
+
+# The number of entries in our test directory.
+n=400000
+
+# Choose a value that is large enough to ensure an accidentally
+# regressed rm would require much longer than $threshold_seconds to remove
+# the directory. With n=400k, pre-patch GNU rm would require about 350
+# seconds even on a fast disk. On a relatively modern system, the
+# patched version of rm requires about 10 seconds, so even if you
+# choose to enable very expensive tests with a disk that is much slower,
+# the test should still succeed.
+
+# Skip unless "." is on an ext[34] file system.
+# FIXME-maybe: try to find a suitable file system or allow
+# the user to specify it via an envvar.
+df -T -t ext3 -t ext4dev -t ext4 . \
+ || skip_ 'this test runs only on an ext3 or ext4 file system'
+
+# Skip if there are too few inodes free. Require some slack.
+free_inodes=$(stat -f --format=%d .) || framework_failure_
+min_free_inodes=$(expr 12 \* $n / 10)
+test $min_free_inodes -lt $free_inodes \
+ || skip_ "too few free inodes on '.': $free_inodes;" \
+ "this test requires at least $min_free_inodes"
+
+ok=0
+start=$(date +%s)
+mkdir d &&
+ cd d &&
+ seq $n | xargs touch &&
+ test -f 1 &&
+ test -f $n &&
+ cd .. &&
+ ok=1
+test $ok = 1 || framework_failure_
+setup_duration=$(expr $(date +%s) - $start)
+echo creating a $n-entry directory took $setup_duration seconds
+
+# If set-up took longer than the default $threshold_seconds,
+# use the longer set-up duration as the limit.
+test $threshold_seconds -lt $setup_duration \
+ && threshold_seconds=$setup_duration
+
+start=$(date +%s)
+timeout ${threshold_seconds}s rm -rf d; err=$?
+duration=$(expr $(date +%s) - $start)
+
+case $err in
+ 124) fail=1; echo rm took longer than $threshold_seconds seconds;;
+ 0) ;;
+ *) fail=1;;
+esac
+
+echo removing a $n-entry directory took $duration seconds
+
+Exit $fail
diff --git a/tests/rm/f-1.sh b/tests/rm/f-1.sh
new file mode 100755
index 0000000..b2c0a79
--- /dev/null
+++ b/tests/rm/f-1.sh
@@ -0,0 +1,26 @@
+#!/bin/sh
+# Test "rm -f" with a nonexistent file.
+
+# Copyright (C) 1997-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_ rm
+
+mkdir -p d || framework_failure_
+
+rm -f d/no-such-file || fail=1
+
+Exit $fail
diff --git a/tests/rm/fail-2eperm.sh b/tests/rm/fail-2eperm.sh
new file mode 100755
index 0000000..efcf705
--- /dev/null
+++ b/tests/rm/fail-2eperm.sh
@@ -0,0 +1,57 @@
+#!/bin/sh
+# Like fail-eperm, but the failure must be for a file encountered
+# while trying to remove the containing directory with the sticky bit set.
+
+# Copyright (C) 2003-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_ rm
+require_root_
+
+# The containing directory must be owned by the user who eventually runs rm.
+chown $NON_ROOT_USERNAME .
+
+mkdir a || framework_failure_
+chmod 1777 a || framework_failure_
+touch a/b || framework_failure_
+
+
+# Try to ensure that $NON_ROOT_USERNAME can access
+# the required version of rm.
+rm_version=$(
+ chroot --skip-chdir --user=$NON_ROOT_USERNAME / env PATH="$PATH" \
+ rm --version |
+ sed -n '1s/.* //p'
+)
+case $rm_version in
+ $PACKAGE_VERSION) ;;
+ *) skip_ "cannot access just-built rm as user $NON_ROOT_USERNAME";;
+esac
+chroot --skip-chdir --user=$NON_ROOT_USERNAME / \
+ env PATH="$PATH" rm -rf a 2> out-t && fail=1
+
+# On some systems, we get 'Not owner'. Convert it.
+# On other systems (HPUX), we get 'Permission denied'. Convert it, too.
+onp='Operation not permitted'
+sed "s/Not owner/$onp/;s/Permission denied/$onp/" out-t > out
+
+cat <<\EOF > exp
+rm: cannot remove 'a/b': Operation not permitted
+EOF
+
+compare exp out || fail=1
+
+Exit $fail
diff --git a/tests/rm/fail-eacces.sh b/tests/rm/fail-eacces.sh
new file mode 100755
index 0000000..3c5db75
--- /dev/null
+++ b/tests/rm/fail-eacces.sh
@@ -0,0 +1,54 @@
+#!/bin/sh
+# Ensure that rm -rf unremovable-non-dir gives a diagnostic.
+# Test both a regular file and a symlink -- it makes a difference to rm.
+# With the symlink, rm from coreutils-6.9 would fail with a misleading
+# ELOOP diagnostic.
+
+# Copyright (C) 2006-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_ rm
+skip_if_root_
+
+ok=0
+mkdir d &&
+ touch d/f &&
+ ln -s f d/slink &&
+ chmod a-w d &&
+ ok=1
+test $ok = 1 || framework_failure_
+
+mkdir e &&
+ ln -s f e/slink &&
+ chmod a-w e &&
+ ok=1
+test $ok = 1 || framework_failure_
+
+
+rm -rf d/f 2> out && fail=1
+cat <<\EOF > exp
+rm: cannot remove 'd/f': Permission denied
+EOF
+compare exp out || fail=1
+
+# This used to fail with ELOOP.
+rm -rf e 2> out && fail=1
+cat <<\EOF > exp
+rm: cannot remove 'e/slink': Permission denied
+EOF
+compare exp out || fail=1
+
+Exit $fail
diff --git a/tests/rm/fail-eperm.xpl b/tests/rm/fail-eperm.xpl
new file mode 100755
index 0000000..89f63d4
--- /dev/null
+++ b/tests/rm/fail-eperm.xpl
@@ -0,0 +1,150 @@
+#!/usr/bin/perl -Tw
+# Ensure that rm gives the expected diagnostic when failing to remove a file
+# owned by some other user in a directory with the sticky bit set.
+
+# Copyright (C) 2002-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/>.
+
+use strict;
+
+(my $ME = $0) =~ s|.*/||;
+
+my $uid = $<;
+# skip if root
+$uid == 0
+ and CuSkip::skip "$ME: can't run this test as root: skipping this test";
+
+my $verbose = $ENV{VERBOSE} && $ENV{VERBOSE} eq 'yes';
+
+# Ensure that the diagnostics are in English.
+$ENV{LC_ALL} = 'C';
+
+# Set up a safe, well-known environment
+$ENV{IFS} = '';
+
+# Taint checking requires a sanitized $PATH. This script performs no $PATH
+# search, so on most Unix-based systems, it is fine simply to clear $ENV{PATH}.
+# However, on Cygwin, it's used to find cygwin1.dll, so set it.
+$ENV{PATH} = '/bin:/usr/bin';
+
+my @dir_list = qw(/tmp /var/tmp /usr/tmp);
+my $rm = "$ENV{abs_top_builddir}/src/rm";
+
+# Untaint for upcoming popen.
+$rm =~ m!^([-+\@\w./]+)$!
+ or CuSkip::skip "$ME: unusual absolute builddir name; skipping this test\n";
+$rm = $1;
+
+# Find a directory with the sticky bit set.
+my $found_dir;
+my $found_file;
+foreach my $dir (@dir_list)
+ {
+ if (-d $dir && -k _ && -r _ && -w _ && -x _)
+ {
+ $found_dir = 1;
+
+ # Find a non-directory there that is owned by some other user.
+ opendir DIR_HANDLE, $dir
+ or die "$ME: couldn't open $dir: $!\n";
+
+ foreach my $f (readdir DIR_HANDLE)
+ {
+ # Consider only names containing "safe" characters.
+ $f =~ /^([-\@\w.]+)$/
+ or next;
+ $f = $1; # untaint $f
+
+ my $target_file = "$dir/$f";
+ $verbose
+ and warn "$ME: considering $target_file\n";
+
+ # Skip files owned by self, symlinks, and directories.
+ # It's not technically necessary to skip symlinks, but it's simpler.
+ # SVR4-like systems (e.g., Solaris 9) let you unlink files that
+ # you can write, so skip writable files too.
+ -l $target_file || -o _ || -d _ || -w _
+ and next;
+
+ $found_file = 1;
+
+ # Invoke rm on this file and ensure that we get the
+ # expected exit code and diagnostic.
+ my $cmd = "$rm -f -- $target_file";
+ open RM, "$cmd 2>&1 |"
+ or die "$ME: cannot execute '$cmd'\n";
+
+ my $line = <RM>;
+
+ close RM;
+ my $rc = $?;
+ # This test opportunistically looks for files that can't
+ # be removed but those files may already have been removed
+ # by their owners by the time we get to them. It is a
+ # race condition. If so then the rm is successful and our
+ # test is thwarted. Detect this case and ignore.
+ if ($rc == 0)
+ {
+ next if ! -e $target_file;
+ die "$ME: unexpected exit status from '$cmd';\n"
+ . " got 0, expected 1\n";
+ }
+ if (0x80 < $rc)
+ {
+ my $status = $rc >> 8;
+ $status == 1
+ or die "$ME: unexpected exit status from '$cmd';\n"
+ . " got $status, expected 1\n";
+ }
+ else
+ {
+ # Terminated by a signal.
+ my $sig_num = $rc & 0x7F;
+ die "$ME: command '$cmd' died with signal $sig_num\n";
+ }
+
+ my $exp = "rm: cannot remove '$target_file':";
+ $line
+ or die "$ME: no output from '$cmd';\n"
+ . "expected something like '$exp ...'\n";
+
+ # Transform the actual diagnostic so that it starts with "rm:".
+ # Depending on your system, it might be "rm:" already, or
+ # "../../src/rm:".
+ $line =~ s,^\Q$rm\E:,rm:,;
+
+ my $regex = quotemeta $exp;
+ $line =~ /^$regex/
+ or die "$ME: unexpected diagnostic from '$cmd';\n"
+ . " got $line"
+ . " expected $exp ...\n";
+
+ last;
+ }
+
+ closedir DIR_HANDLE;
+ $found_file
+ and last;
+ }
+ }
+
+$found_dir
+ or CuSkip::skip "$ME: couldn't find a directory with the sticky bit set;"
+ . " skipping this test\n";
+
+$found_file
+ or CuSkip::skip "$ME: couldn't find a file not owned by you\n"
+ . " in any of the following directories:\n @dir_list\n"
+ . "...so, skipping this test\n";
diff --git a/tests/rm/hash.sh b/tests/rm/hash.sh
new file mode 100755
index 0000000..0ebae8b
--- /dev/null
+++ b/tests/rm/hash.sh
@@ -0,0 +1,37 @@
+#!/bin/sh
+# Exercise a bug that was fixed in 4.0s.
+# Before then, rm would fail occasionally, sometimes via
+# a failed assertion, others with a seg fault.
+
+# Copyright (C) 2000-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_ rm
+expensive_
+
+# Create a hierarchy with 3*26 leaf directories, each at depth 153.
+echo "$0: creating 78 trees, each of depth 153; this will take a while..." >&2
+y=$(seq 1 150|tr -sc '\n' y|tr '\n' /)
+for i in 1 2 3; do
+ for j in a b c d e f g h i j k l m n o p q r s t u v w x y z; do
+ mkdir -p t/$i/$j/$y || framework_failure_
+ done
+done
+
+
+rm -r t || fail=1
+
+Exit $fail
diff --git a/tests/rm/i-1.sh b/tests/rm/i-1.sh
new file mode 100755
index 0000000..ea6fed8
--- /dev/null
+++ b/tests/rm/i-1.sh
@@ -0,0 +1,40 @@
+#!/bin/sh
+# Test "rm -i".
+
+# Copyright (C) 1997-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_ rm
+
+t=t
+mkdir -p $t || framework_failure_
+echo > $t/a || framework_failure_
+test -f $t/a || framework_failure_
+
+echo y > $t/in-y
+echo n > $t/in-n
+
+rm -i $t/a < $t/in-n > /dev/null 2>&1 || fail=1
+# The file should not have been removed.
+test -f $t/a || fail=1
+
+rm -i $t/a < $t/in-y > /dev/null 2>&1 || fail=1
+# The file should have been removed this time.
+test -f $t/a && fail=1
+
+rm -rf $t
+
+Exit $fail
diff --git a/tests/rm/i-never.sh b/tests/rm/i-never.sh
new file mode 100755
index 0000000..fb539e3
--- /dev/null
+++ b/tests/rm/i-never.sh
@@ -0,0 +1,33 @@
+#!/bin/sh
+# Ensure that rm --interactive=never works does not prompt, even for
+# an unwritable file.
+
+# Copyright (C) 2007-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_ rm
+skip_if_root_
+
+touch f || framework_failure_
+chmod 0 f || framework_failure_
+touch exp || framework_failure_
+
+
+rm --interactive=never f > out || fail=1
+
+compare exp out || fail=1
+
+Exit $fail
diff --git a/tests/rm/i-no-r.sh b/tests/rm/i-no-r.sh
new file mode 100755
index 0000000..1a6e8e6
--- /dev/null
+++ b/tests/rm/i-no-r.sh
@@ -0,0 +1,34 @@
+#!/bin/sh
+# Since the rewrite for fileutils-4.1.9, 'rm -i DIR' would mistakenly
+# recurse into directory DIR. rm -i (without -r) must fail in that case.
+# Fixed in coreutils-4.5.2.
+
+# Copyright (C) 2002-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_ rm
+
+mkdir dir || framework_failure_
+echo y > y || framework_failure_
+
+
+# This must fail.
+returns_ 1 rm -i dir < y > /dev/null 2>&1 || fail=1
+
+# The directory must remain.
+test -d dir || fail=1
+
+Exit $fail
diff --git a/tests/rm/ignorable.sh b/tests/rm/ignorable.sh
new file mode 100755
index 0000000..59a89aa
--- /dev/null
+++ b/tests/rm/ignorable.sh
@@ -0,0 +1,30 @@
+#!/bin/sh
+# Ensure that rm -f existing-non-dir/anything exits successfully
+
+# Copyright (C) 2006-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_ rm
+skip_if_root_
+
+touch existing-non-dir || framework_failure_
+
+
+# With coreutils-6.3, this would exit nonzero. It should not.
+# Example from Andreas Schwab.
+rm -f existing-non-dir/f > out 2>&1 || fail=1
+
+Exit $fail
diff --git a/tests/rm/inaccessible.sh b/tests/rm/inaccessible.sh
new file mode 100755
index 0000000..eafddb4
--- /dev/null
+++ b/tests/rm/inaccessible.sh
@@ -0,0 +1,49 @@
+#!/bin/sh
+# Ensure that rm works even when run from a directory
+# for which the user has no access at all.
+
+# Copyright (C) 2004-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_ rm
+
+# Skip this test if your system has neither the openat-style functions
+# nor /proc/self/fd support with which to emulate them.
+require_openat_support_
+skip_if_root_
+
+p=$(pwd)
+mkdir abs1 abs2 no-access || framework_failure_
+
+
+set +x
+(cd no-access; chmod 0 . && rm -r "$p/abs1" rel "$p/abs2") 2> out && fail=1
+test -d "$p/abs1" && fail=1
+test -d "$p/abs2" && fail=1
+
+cat <<\EOF > exp || framework_failure_
+rm: cannot remove 'rel': Permission denied
+EOF
+
+# AIX 4.3.3 fails with a different diagnostic.
+# Transform their diagnostic
+# ...: The file access permissions do not allow the specified action.
+# to the expected one:
+sed 's/: The file access permissions.*/: Permission denied/'<out>o1;mv o1 out
+
+compare exp out || fail=1
+
+Exit $fail
diff --git a/tests/rm/interactive-always.sh b/tests/rm/interactive-always.sh
new file mode 100755
index 0000000..424086c
--- /dev/null
+++ b/tests/rm/interactive-always.sh
@@ -0,0 +1,87 @@
+#!/bin/sh
+# Test the --interactive[=WHEN] changes added to coreutils 6.0
+
+# Copyright (C) 2006-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_ rm
+
+touch file1-1 file1-2 file2-1 file2-2 file3-1 file3-2 file4-1 file4-2 \
+ || framework_failure_
+# If asked, answer no to first question, then yes to second.
+echo 'n
+y' > in || framework_failure_
+rm -f out err || framework_failure_
+
+
+# The prompt has a trailing space, and no newline, so an extra
+# 'echo .' is inserted after each rm to make it obvious what was asked.
+
+echo 'no WHEN' > err || framework_failure_
+rm -R --interactive file1-* < in >> out 2>> err || fail=1
+echo . >> err || framework_failure_
+test -f file1-1 || fail=1
+test -f file1-2 && fail=1
+
+echo 'WHEN=never' >> err || framework_failure_
+rm -R --interactive=never file2-* < in >> out 2>> err || fail=1
+echo . >> err || framework_failure_
+test -f file2-1 && fail=1
+test -f file2-2 && fail=1
+
+echo 'WHEN=once' >> err || framework_failure_
+rm -R --interactive=once file3-* < in >> out 2>> err || fail=1
+echo . >> err || framework_failure_
+test -f file3-1 || fail=1
+test -f file3-2 || fail=1
+
+echo 'WHEN=always' >> err || framework_failure_
+rm -R --interactive=always file4-* < in >> out 2>> err || fail=1
+echo . >> err || framework_failure_
+test -f file4-1 || fail=1
+test -f file4-2 && fail=1
+
+echo '-f overrides --interactive' >> err || framework_failure_
+rm -R --interactive=once -f file1-* < in >> out 2>> err || fail=1
+echo . >> err || framework_failure_
+test -f file1-1 && fail=1
+
+echo '--interactive overrides -f' >> err || framework_failure_
+rm -R -f --interactive=once file4-* < in >> out 2>> err || fail=1
+echo . >> err || framework_failure_
+test -f file4-1 || fail=1
+
+cat <<\EOF > experr.t || framework_failure_
+no WHEN
+@remove_empty 'file1-1'? @remove_empty 'file1-2'? .
+WHEN=never
+.
+WHEN=once
+rm: remove 2 arguments recursively? .
+WHEN=always
+@remove_empty 'file4-1'? @remove_empty 'file4-2'? .
+-f overrides --interactive
+.
+--interactive overrides -f
+rm: remove 1 argument recursively? .
+EOF
+sed 's/@remove_empty/rm: remove regular empty file/g' < experr.t > experr ||
+ framework_failure_
+
+compare /dev/null out || fail=1
+compare experr err || fail=1
+
+Exit $fail
diff --git a/tests/rm/interactive-once.sh b/tests/rm/interactive-once.sh
new file mode 100755
index 0000000..084cdb6
--- /dev/null
+++ b/tests/rm/interactive-once.sh
@@ -0,0 +1,148 @@
+#!/bin/sh
+# Test the -I option added to coreutils 6.0
+
+# Copyright (C) 2006-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_ rm
+
+mkdir -p dir1-1 dir2-1 dir2-2 || framework_failure_
+touch file1-1 file2-1 file2-2 file2-3 file3-1 file3-2 file3-3 file3-4 \
+ || framework_failure_
+echo y > in-y || framework_failure_
+echo n > in-n || framework_failure_
+rm -f out err || framework_failure_
+
+
+# The prompt has a trailing space, and no newline, so an extra
+# 'echo .' is inserted after each rm to make it obvious what was asked.
+
+echo 'one file, no recursion' > err || framework_failure_
+rm -I file1-* < in-n >> out 2>> err || fail=1
+echo . >> err || framework_failure_
+test -f file1-1 && fail=1
+
+echo 'one file, read only, answer no' >> err || framework_failure_
+if ls /dev/stdin >/dev/null 2>&1; then
+ touch file1-1 || framework_failure_
+ chmod a-w file1-1 || framework_failure_
+ if ! test -w file1-1; then
+ # root won't get prompted
+ write_prot_msg1="rm: remove write-protected regular empty file 'file1-1'? "
+ fi
+ rm ---presume-input-tty -I file1-* < in-n >> out 2>> err || fail=1
+ echo . >> err || framework_failure_
+ if test "$write_prot_msg1"; then
+ test -f file1-1 || fail=1
+ fi
+else
+ echo '.' >> err || framework_failure_
+fi
+
+echo 'three files, no recursion' >> err || framework_failure_
+rm -I file2-* < in-n >> out 2>> err || fail=1
+echo . >> err || framework_failure_
+test -f file2-1 && fail=1
+test -f file2-2 && fail=1
+test -f file2-3 && fail=1
+
+echo 'four files, no recursion, answer no' >> err || framework_failure_
+rm -I file3-* < in-n >> out 2>> err || fail=1
+echo . >> err || framework_failure_
+test -f file3-1 || fail=1
+test -f file3-2 || fail=1
+test -f file3-3 || fail=1
+test -f file3-4 || fail=1
+
+echo 'four files, no recursion, answer yes' >> err || framework_failure_
+rm -I file3-* < in-y >> out 2>> err || fail=1
+echo . >> err || framework_failure_
+test -f file3-1 && fail=1
+test -f file3-2 && fail=1
+test -f file3-3 && fail=1
+test -f file3-4 && fail=1
+
+echo 'four files, no recursion, 1 read only, answer yes no' >> err \
+ || framework_failure_
+if ls /dev/stdin >/dev/null 2>&1; then
+ touch file3-1 file3-2 file3-3 file3-4 || framework_failure_
+ echo non_empty > file3-4 || framework_failure_ # to shorten diagnostic
+ chmod a-w file3-4 || framework_failure_
+ if ! test -w file3-4; then
+ # root won't get prompted
+ write_prot_msg2="rm: remove write-protected regular file 'file3-4'? "
+ fi
+ cat in-y in-n | rm ---presume-input-tty -I file3-* >> out 2>> err || fail=1
+ echo . >> err || framework_failure_
+ test -f file3-1 && fail=1
+ test -f file3-2 && fail=1
+ test -f file3-3 && fail=1
+ if test "$write_prot_msg2"; then
+ test -f file3-4 || fail=1
+ fi
+else
+ echo 'rm: remove 4 arguments? .' >> err || framework_failure_
+fi
+
+echo 'one file, recursion, answer no' >> err || framework_failure_
+rm -I -R dir1-* < in-n >> out 2>> err || fail=1
+echo . >> err || framework_failure_
+test -d dir1-1 || fail=1
+
+echo 'one file, recursion, answer yes' >> err || framework_failure_
+rm -I -R dir1-* < in-y >> out 2>> err || fail=1
+echo . >> err || framework_failure_
+test -d dir1-1 && fail=1
+
+echo 'multiple files, recursion, answer no' >> err || framework_failure_
+rm -I -R dir2-* < in-n >> out 2>> err || fail=1
+echo . >> err || framework_failure_
+test -d dir2-1 || fail=1
+test -d dir2-2 || fail=1
+
+echo 'multiple files, recursion, answer yes' >> err || framework_failure_
+rm -I -R dir2-* < in-y >> out 2>> err || fail=1
+echo . >> err || framework_failure_
+test -d dir2-1 && fail=1
+test -d dir2-2 && fail=1
+
+cat <<EOF > experr || framework_failure_
+one file, no recursion
+.
+one file, read only, answer no
+$write_prot_msg1.
+three files, no recursion
+.
+four files, no recursion, answer no
+rm: remove 4 arguments? .
+four files, no recursion, answer yes
+rm: remove 4 arguments? .
+four files, no recursion, 1 read only, answer yes no
+rm: remove 4 arguments? $write_prot_msg2.
+one file, recursion, answer no
+rm: remove 1 argument recursively? .
+one file, recursion, answer yes
+rm: remove 1 argument recursively? .
+multiple files, recursion, answer no
+rm: remove 2 arguments recursively? .
+multiple files, recursion, answer yes
+rm: remove 2 arguments recursively? .
+EOF
+
+compare /dev/null out || fail=1
+compare experr err || fail=1
+
+Exit $fail
diff --git a/tests/rm/ir-1.sh b/tests/rm/ir-1.sh
new file mode 100755
index 0000000..0e06fd6
--- /dev/null
+++ b/tests/rm/ir-1.sh
@@ -0,0 +1,56 @@
+#!/bin/sh
+# Test "rm -ir".
+
+# Copyright (C) 1997-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_ rm
+
+t=t
+mkdir -p $t $t/a $t/b $t/c || framework_failure_
+> $t/a/a || framework_failure_
+> $t/b/bb || framework_failure_
+> $t/c/cc || framework_failure_
+
+cat <<EOF > in
+y
+y
+y
+y
+y
+y
+y
+y
+n
+n
+n
+EOF
+
+# Remove all but one of a, b, c -- I doubt that this test can portably
+# determine which one was removed based on order of dir entries.
+# This is a good argument for switching to a dejagnu-style test suite.
+rm --verbose -i -r $t < in > /dev/null 2>&1 || fail=1
+
+# $t should not have been removed.
+test -d $t || fail=1
+
+# There should be only one directory left.
+case $(echo $t/*) in
+ $t/[abc]) ;;
+ *) fail=1 ;;
+esac
+
+Exit $fail
diff --git a/tests/rm/isatty.sh b/tests/rm/isatty.sh
new file mode 100755
index 0000000..0d7838e
--- /dev/null
+++ b/tests/rm/isatty.sh
@@ -0,0 +1,55 @@
+#!/bin/sh
+# Make sure 'chown 0 f; rm f' prompts before removing f.
+
+# Copyright (C) 2001-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_ rm
+skip_if_root_
+
+
+# Skip this test if there is no /dev/stdin file.
+ls /dev/stdin >/dev/null 2>&1 \
+ || skip_ 'there is no /dev/stdin file'
+
+# Terminate any background processes
+cleanup_() { kill $pid 2>/dev/null && wait $pid; }
+
+touch f
+chmod 0 f
+rm ---presume-input-tty f > out 2>&1 & pid=$!
+
+# Wait a second, to give a buggy rm (as in fileutils-4.0.40)
+# enough time to remove the file.
+sleep 1
+
+# The file must still exist.
+test -f f || fail=1
+
+cleanup_
+
+# Note the trailing 'x' -- so I don't have to have a trailing
+# blank in this file :-)
+cat > exp <<\EOF
+rm: remove write-protected regular empty file 'f'? x
+EOF
+
+# Append an 'x' and a newline.
+echo x >> out
+
+compare exp out || fail=1
+
+Exit $fail
diff --git a/tests/rm/many-dir-entries-vs-OOM.sh b/tests/rm/many-dir-entries-vs-OOM.sh
new file mode 100755
index 0000000..acbed3a
--- /dev/null
+++ b/tests/rm/many-dir-entries-vs-OOM.sh
@@ -0,0 +1,47 @@
+#!/bin/sh
+# In coreutils-8.12, rm,du,chmod, etc. would use too much memory
+# when processing a directory with many entries (as in > 100,000).
+
+# Copyright (C) 2011-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_ rm du chmod
+expensive_
+
+mkdir d2 \
+ && touch d2/f || framework_failure_
+
+# Restrict memory. Each of these coreutils-8.12 programs would fail
+# with a diagnostic like "rm: fts_read failed: Cannot allocate memory".
+vm=$(get_min_ulimit_v_ du -sh d2) \
+ || skip_ "this shell lacks ulimit support"
+
+# With many files in a single directory...
+mkdir d || framework_failure_
+seq --format="d/%06g" 200000 | xargs touch || framework_failure_
+
+# Allow 35MiB more memory as for the trivial case above.
+(ulimit -v $(($vm + 35000)) && du -sh d) || fail=1
+
+vm=$(get_min_ulimit_v_ chmod -R 700 d2) \
+ || skip_ "this shell lacks ulimit support"
+(ulimit -v $(($vm + 35000)) && chmod -R 700 d) || fail=1
+
+vm=$(get_min_ulimit_v_ rm -rf d2) \
+ || skip_ "this shell lacks ulimit support"
+(ulimit -v $(($vm + 35000)) && rm -rf d) || fail=1
+
+Exit $fail
diff --git a/tests/rm/no-give-up.sh b/tests/rm/no-give-up.sh
new file mode 100755
index 0000000..b5bd706
--- /dev/null
+++ b/tests/rm/no-give-up.sh
@@ -0,0 +1,42 @@
+#!/bin/sh
+# With rm from coreutils-5.2.1 and earlier, 'rm -r' would mistakenly
+# give up too early under some conditions.
+
+# Copyright (C) 2004-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_ rm
+require_root_
+
+mkdir d || framework_failure_
+touch d/f || framework_failure_
+chown -R $NON_ROOT_USERNAME d || framework_failure_
+
+# Ensure that non-root can access files in root-owned ".".
+chmod go=x . || framework_failure_
+
+
+# This must fail, since '.' is not writable by $NON_ROOT_USERNAME.
+returns_ 1 chroot --skip-chdir --user=$NON_ROOT_USERNAME / env PATH="$PATH" \
+ rm -rf d 2>/dev/null || fail=1
+
+# d must remain.
+test -d d || fail=1
+
+# f must have been removed.
+test -f d/f && fail=1
+
+Exit $fail
diff --git a/tests/rm/one-file-system.sh b/tests/rm/one-file-system.sh
new file mode 100755
index 0000000..0ec7693
--- /dev/null
+++ b/tests/rm/one-file-system.sh
@@ -0,0 +1,51 @@
+#!/bin/sh
+# Demonstrate rm's new --one-file-system option.
+
+# Copyright (C) 2006-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_ rm
+require_root_
+
+cleanup_()
+{
+ # When you take the undesirable shortcut of making /etc/mtab a link
+ # to /proc/mounts, unmounting "$other_partition_tmpdir" would fail.
+ # So, here we unmount a/b instead.
+ umount a/b
+ rm -rf "$other_partition_tmpdir"
+}
+. "$abs_srcdir/tests/other-fs-tmpdir"
+
+t=$other_partition_tmpdir
+mkdir -p a/b $t/y
+mount --bind $t a/b \
+ || skip_ "This test requires mount with a working --bind option."
+
+cat <<\EOF > exp || framework_failure_
+rm: skipping 'a/b', since it's on a different device
+EOF
+returns_ 1 rm --one-file-system -rf a 2> out || fail=1
+test -d $t/y || fail=1
+compare exp out || fail=1
+
+cat <<\EOF >> exp || framework_failure_
+rm: and --preserve-root=all is in effect
+EOF
+returns_ 1 rm --preserve-root=all -rf a/b 2>out || fail=1
+compare exp out || fail=1
+
+Exit $fail
diff --git a/tests/rm/one-file-system2.sh b/tests/rm/one-file-system2.sh
new file mode 100755
index 0000000..7000c7a
--- /dev/null
+++ b/tests/rm/one-file-system2.sh
@@ -0,0 +1,27 @@
+#!/bin/sh
+# Verify --one-file-system does delete within a file system
+
+# Copyright (C) 2009-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_ rm
+
+mkdir -p a/b
+
+rm --one-file-system -rf a || fail=1
+test -d a && fail=1
+
+Exit $fail
diff --git a/tests/rm/r-1.sh b/tests/rm/r-1.sh
new file mode 100755
index 0000000..4c5b748
--- /dev/null
+++ b/tests/rm/r-1.sh
@@ -0,0 +1,40 @@
+#!/bin/sh
+# Test "rm -r --verbose".
+
+# Copyright (C) 1997-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_ rm
+
+mkdir a a/a || framework_failure_
+> b || framework_failure_
+
+cat <<\EOF > exp || framework_failure_
+removed directory 'a/a'
+removed directory 'a'
+removed 'b'
+EOF
+
+rm --verbose -r a b > out || fail=1
+
+for d in $dirs; do
+ test -d $d && fail=1
+done
+
+# Compare expected and actual output.
+compare exp out || fail=1
+
+Exit $fail
diff --git a/tests/rm/r-2.sh b/tests/rm/r-2.sh
new file mode 100755
index 0000000..ae3440d
--- /dev/null
+++ b/tests/rm/r-2.sh
@@ -0,0 +1,45 @@
+#!/bin/sh
+# Test "rm -r --verbose".
+
+# Copyright (C) 1997-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_ rm
+
+mkdir t t/a t/a/b || framework_failure_
+> t/a/f || framework_failure_
+> t/a/b/g || framework_failure_
+
+# FIXME: if this fails, it's a framework failure
+cat <<\EOF | sort > t/E || framework_failure_
+removed directory 't/a'
+removed directory 't/a/b'
+removed 't/a/b/g'
+removed 't/a/f'
+EOF
+
+# Note that both the expected output (above) and the actual output lines
+# are sorted, because directory entries may be processed in arbitrary order.
+rm --verbose -r t/a | sort > t/O || fail=1
+
+if test -d t/a; then
+ fail=1
+fi
+
+# Compare expected and actual output.
+cmp t/E t/O || fail=1
+
+Exit $fail
diff --git a/tests/rm/r-3.sh b/tests/rm/r-3.sh
new file mode 100755
index 0000000..c0d719b
--- /dev/null
+++ b/tests/rm/r-3.sh
@@ -0,0 +1,48 @@
+#!/bin/sh
+# Create and remove a directory with more than 254 files.
+
+# Copyright (C) 1997-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/>.
+
+
+# An early version of my rewritten rm failed to remove all of
+# the files on SunOS4 when there were 254 or more in a directory.
+
+# And the rm from coreutils-5.0 exposes the same problem when there
+# are 338 or more files in a directory on a Darwin-6.5 system
+
+. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src
+print_ver_ rm
+
+mkdir t || framework_failure_
+cd t || framework_failure_
+
+# Create 500 files (20 * 25).
+for i in 0 1 2 3 4 5 6 7 8 9 a b c d e f g h i j; do
+ files=
+ for j in a b c d e f g h i j k l m n o p q r s t u v w x y; do
+ files="$files $i$j"
+ done
+ touch $files || framework_failure_
+done
+
+test -f 0a || framework_failure_
+test -f by || framework_failure_
+cd .. || framework_failure_
+
+rm -rf t || fail=1
+test -d t && fail=1
+
+Exit $fail
diff --git a/tests/rm/r-4.sh b/tests/rm/r-4.sh
new file mode 100755
index 0000000..49d57d0
--- /dev/null
+++ b/tests/rm/r-4.sh
@@ -0,0 +1,51 @@
+#!/bin/sh
+# Try to remove '.' and '..' recursively.
+
+# Copyright (C) 2006-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_ rm
+
+mkdir d || framework_failure_
+touch d/a || framework_failure_
+
+# Expected error diagnostic as grep pattern.
+exp="^rm: refusing to remove '\.' or '\.\.' directory: skipping '.*'\$"
+
+rmtest()
+{
+ # Try removing - expecting failure.
+ rm -fr "$1" 2> err && fail=1
+
+ # Ensure the expected error diagnostic is output.
+ grep "$exp" err || { cat err; fail=1; }
+
+ return $fail
+}
+
+rmtest 'd/.' || fail=1
+rmtest 'd/./' || fail=1
+rmtest 'd/.////' || fail=1
+rmtest 'd/..' || fail=1
+rmtest 'd/../' || fail=1
+
+
+# This test is handled more carefully in r-root.sh
+# returns_ 1 rm -fr / 2>/dev/null || fail=1
+
+test -f d/a || fail=1
+
+Exit $fail
diff --git a/tests/rm/r-root.sh b/tests/rm/r-root.sh
new file mode 100755
index 0000000..7dc618f
--- /dev/null
+++ b/tests/rm/r-root.sh
@@ -0,0 +1,320 @@
+#!/bin/sh
+# Try to remove '/' recursively.
+
+# Copyright (C) 2013-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_ rm
+
+# POSIX mandates rm(1) to skip '/' arguments. This test verifies this mandated
+# behavior as well as the --preserve-root and --no-preserve-root options.
+# Especially the latter case is a live fire exercise as rm(1) is supposed to
+# enter the unlinkat() system call. Therefore, limit the risk as much
+# as possible -- if there's a bug this test would wipe the system out!
+
+# Faint-hearted: skip this test for the 'root' user.
+skip_if_root_
+
+# Pull the teeth from rm(1) by intercepting the unlinkat() system call via the
+# LD_PRELOAD environment variable. This requires shared libraries to work.
+require_gcc_shared_
+
+# Ensure this variable is unset as it's
+# used later in the unlinkat() wrapper.
+unset CU_TEST_SKIP_EXIT
+
+# Set this to 0 if you don't have a working gdb but would
+# still like to run the test
+USE_GDB=1
+
+if test $USE_GDB = 1; then
+ # Use gdb to provide further protection by limiting calls to unlinkat().
+ ( timeout 10s gdb --version ) > gdb.out 2>&1
+ case $(cat gdb.out) in
+ *'GNU gdb'*) ;;
+ *) skip_ "can't run gdb";;
+ esac
+fi
+
+# Break on a line rather than a symbol, to cater for inline functions
+break_src="$abs_top_srcdir/src/remove.c"
+break_line=$(grep -n ^excise "$break_src") || framework_failure_
+break_line=$(echo "$break_line" | cut -d: -f1) || framework_failure_
+break_line="$break_src:$break_line"
+
+
+cat > k.c <<'EOF' || framework_failure_
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+int unlinkat (int dirfd, const char *pathname, int flags)
+{
+ /* Prove that LD_PRELOAD works: create the evidence file "x". */
+ fclose (fopen ("x", "w"));
+
+ /* Immediately terminate, unless indicated otherwise. */
+ if (! getenv("CU_TEST_SKIP_EXIT"))
+ _exit (0);
+
+ /* Pretend success. */
+ return 0;
+}
+EOF
+
+# Then compile/link it:
+gcc_shared_ k.c k.so \
+ || framework_failure_ 'failed to build shared library'
+
+# Note breakpoint commands don't work in batch mode
+# https://sourceware.org/bugzilla/show_bug.cgi?id=10079
+# So we use python to script behavior upon hitting the breakpoint
+cat > bp.py <<'EOF.py' || framework_failure_
+def breakpoint_handler (event):
+ if not isinstance(event, gdb.BreakpointEvent):
+ return
+ hit_count = event.breakpoints[0].hit_count
+ if hit_count == 1:
+ gdb.execute('shell touch excise.break')
+ gdb.execute('continue')
+ elif hit_count > 2:
+ gdb.write('breakpoint hit twice already')
+ gdb.execute('quit 1')
+ else:
+ gdb.execute('continue')
+
+gdb.events.stop.connect(breakpoint_handler)
+EOF.py
+
+# In order of the sed expressions below, this cleans:
+#
+# 1. gdb uses the full path when running rm, so remove the leading dirs.
+# 2. For some of the "/" synonyms, the error diagnostic slightly differs from
+# that of the basic "/" case (see gnulib's fts_open' and ROOT_DEV_INO_WARN):
+# rm: it is dangerous to operate recursively on 'FILE' (same as '/')
+# Strip that part off for the following comparison.
+clean_rm_err_()
+{
+ sed "s/.*rm: /rm: /; \
+ s/\(rm: it is dangerous to operate recursively on\).*$/\1 '\/'/"
+}
+
+#-------------------------------------------------------------------------------
+# exercise_rm_r_root: shell function to test "rm -r '/'"
+# The caller must provide the FILE to remove as well as any options
+# which should be passed to 'rm'.
+# Paranoia mode on:
+# For the worst case where both rm(1) would fail to refuse to process the "/"
+# argument (in the cases without the --no-preserve-root option), and
+# intercepting the unlinkat(1) system call would fail (which actually already
+# has been proven to work above), and the current non root user has
+# write access to "/", limit the damage to the current file system via
+# the --one-file-system option.
+# Furthermore, run rm(1) via gdb that limits the number of unlinkat() calls.
+exercise_rm_r_root ()
+{
+ # Remove the evidence files; verify that.
+ rm -f x excise.break || framework_failure_
+ test -f x && framework_failure_
+ test -f excise.break && framework_failure_
+
+ local skip_exit=
+ if [ "$CU_TEST_SKIP_EXIT" = 1 ]; then
+ # Pass on this variable into 'rm's environment.
+ skip_exit='CU_TEST_SKIP_EXIT=1'
+ fi
+
+ if test $USE_GDB = 1; then
+ gdb -nx --batch-silent -return-child-result \
+ --eval-command="set exec-wrapper \
+ env 'LD_PRELOAD=$LD_PRELOAD:./k.so' $skip_exit" \
+ --eval-command="break '$break_line'" \
+ --eval-command='source bp.py' \
+ --eval-command="run -rv --one-file-system $*" \
+ --eval-command='quit' \
+ rm < /dev/null > out 2> err.t
+ else
+ touch excise.break
+ env LD_PRELOAD=$LD_PRELOAD:./k.so $skip_exit \
+ rm -rv --one-file-system $* < /dev/null > out 2> err.t
+ fi
+
+ ret=$?
+
+ clean_rm_err_ < err.t > err || ret=$?
+
+ return $ret
+}
+
+# Verify that "rm -r dir" basically works.
+mkdir dir || framework_failure_
+rm -r dir || framework_failure_
+test -d dir && framework_failure_
+
+# Now verify that intercepting unlinkat() works:
+# rm(1) must succeed as before, but this time both the evidence file "x"
+# and the test file / directory must still exist afterward.
+mkdir dir || framework_failure_
+> file || framework_failure_
+
+skip=
+for file in dir file ; do
+ exercise_rm_r_root "$file" || skip=1
+ test -e "$file" || skip=1
+ test -f x || skip=1
+ test -f excise.break || skip=1 # gdb works and breakpoint hit
+ compare /dev/null err || skip=1
+
+ test "$skip" = 1 \
+ && { cat out; cat err; \
+ skip_ "internal test failure: maybe LD_PRELOAD or gdb doesn't work?"; }
+done
+
+# "rm -r /" without --no-preserve-root should output the following
+# diagnostic error message.
+cat <<EOD > exp || framework_failure_
+rm: it is dangerous to operate recursively on '/'
+rm: use --no-preserve-root to override this failsafe
+EOD
+
+#-------------------------------------------------------------------------------
+# Exercise "rm -r /" without and with the --preserve-root option.
+# Exercise various synonyms of "/" including symlinks to it.
+# Expect a non-Zero exit status.
+# Prepare a few symlinks to "/".
+ln -s / rootlink || framework_failure_
+ln -s rootlink rootlink2 || framework_failure_
+ln -sr / rootlink3 || framework_failure_
+
+for opts in \
+ '/' \
+ '--preserve-root /' \
+ '//' \
+ '///' \
+ '////' \
+ 'rootlink/' \
+ 'rootlink2/' \
+ 'rootlink3/' ; do
+
+ returns_ 1 exercise_rm_r_root $opts || fail=1
+
+ # Expect nothing in 'out' and the above error diagnostic in 'err'.
+ # As rm(1) should have skipped the "/" argument, it does not call unlinkat().
+ # Therefore, the evidence file "x" should not exist.
+ compare /dev/null out || fail=1
+ compare exp err || fail=1
+ test -f x && fail=1
+
+ # Do nothing more if this test failed.
+ test $fail = 1 && { cat out; cat err; Exit $fail; }
+done
+
+#-------------------------------------------------------------------------------
+# Exercise with --no-preserve to ensure shortened equivalent is not allowed.
+cat <<EOD > exp_opt || framework_failure_
+rm: you may not abbreviate the --no-preserve-root option
+EOD
+returns_ 1 exercise_rm_r_root --no-preserve / || fail=1
+compare exp_opt err || fail=1
+test -f x && fail=1
+
+#-------------------------------------------------------------------------------
+# Exercise "rm -r file1 / file2".
+# Expect a non-Zero exit status representing failure to remove "/",
+# yet 'file1' and 'file2' should be removed.
+> file1 || framework_failure_
+> file2 || framework_failure_
+
+# Now that we know that 'rm' won't call the unlinkat() system function for "/",
+# we could probably execute it without the LD_PRELOAD'ed safety net.
+# Nevertheless, it's still better to use it for this test.
+# Tell the unlinkat() replacement function to not _exit(0) immediately
+# by setting the following variable.
+CU_TEST_SKIP_EXIT=1
+
+returns_ 1 exercise_rm_r_root --preserve-root file1 '/' file2 || fail=1
+
+unset CU_TEST_SKIP_EXIT
+
+cat <<EOD > out_removed
+removed 'file1'
+removed 'file2'
+EOD
+
+# The above error diagnostic should appear in 'err'.
+# Both 'file1' and 'file2' should be removed. Simply verify that in the
+# "out" file, as the replacement unlinkat() dummy did not remove them.
+# Expect the evidence file "x" to exist.
+compare out_removed out || fail=1
+compare exp err || fail=1
+test -f x || fail=1
+
+# Do nothing more if this test failed.
+test $fail = 1 && { cat out; cat err; Exit $fail; }
+
+#-------------------------------------------------------------------------------
+# Exercise various synonyms of "/" having a trailing "." or ".." in the name.
+# This triggers another check in the code first and therefore leads to a
+# different diagnostic. However, we want to test anyway to protect against
+# future reordering of the checks in the code.
+# Expect that other error diagnostic in 'err' and nothing in 'out'.
+# Expect a non-Zero exit status. The evidence file "x" should not exist.
+for file in \
+ '//.' \
+ '/./' \
+ '/.//' \
+ '/../' \
+ '/.././' \
+ '/etc/..' \
+ 'rootlink/..' \
+ 'rootlink2/.' \
+ 'rootlink3/./' ; do
+
+ test -d "$file" || continue # if e.g. /etc does not exist.
+
+ returns_ 1 exercise_rm_r_root --preserve-root "$file" || fail=1
+
+ grep "rm: refusing to remove '\.' or '\.\.' directory: skipping" err \
+ || fail=1
+
+ compare /dev/null out || fail=1
+ test -f x && fail=1
+
+ # Do nothing more if this test failed.
+ test $fail = 1 && { cat out; cat err; Exit $fail; }
+done
+
+#-------------------------------------------------------------------------------
+# Until now, it was all just fun.
+# Now exercise the --no-preserve-root option with which rm(1) should enter
+# the intercepted unlinkat() system call.
+# As the interception code terminates the process immediately via _exit(0),
+# the exit status should be 0.
+# Use the option --interactive=never to bypass the following prompt:
+# "rm: descend into write-protected directory '/'?"
+exercise_rm_r_root --interactive=never --no-preserve-root '/' \
+ || fail=1
+
+# The 'err' file should not contain the above error diagnostic.
+grep "rm: it is dangerous to operate recursively on '/'" err && fail=1
+
+# Instead, rm(1) should have called the intercepted unlinkat() function,
+# i.e., the evidence file "x" should exist.
+test -f x || fail=1
+
+test $fail = 1 && { cat out; cat err; }
+
+Exit $fail
diff --git a/tests/rm/read-only.sh b/tests/rm/read-only.sh
new file mode 100755
index 0000000..600a17c
--- /dev/null
+++ b/tests/rm/read-only.sh
@@ -0,0 +1,52 @@
+#!/bin/sh
+# Ensure that rm -f nonexistent-file-on-read-only-fs succeeds.
+
+# Copyright (C) 2009-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_ rm
+require_root_
+
+cwd=$(pwd)
+cleanup_() { cd /; umount "$cwd/mnt"; }
+
+skip=0
+# Create a file system, then mount it.
+dd if=/dev/zero of=blob bs=8192 count=200 > /dev/null 2>&1 \
+ || skip=1
+mkdir mnt || skip=1
+mkfs -t ext2 -F blob \
+ || skip_ "failed to create ext2 file system"
+
+mount -oloop blob mnt || skip=1
+echo test > mnt/f || skip=1
+test -s mnt/f || skip=1
+mount -o remount,loop,ro mnt || skip=1
+
+test $skip = 1 \
+ && skip_ "insufficient mount/ext2 support"
+
+# Applying rm -f to a nonexistent file on a read-only file system must succeed.
+rm -f mnt/no-such > out 2>&1 || fail=1
+# It must produce no diagnostic.
+compare /dev/null out || fail=1
+
+# However, trying to remove an existing file must fail.
+rm -f mnt/f > out 2>&1 && fail=1
+# with a diagnostic.
+compare /dev/null out && fail=1
+
+Exit $fail
diff --git a/tests/rm/readdir-bug.sh b/tests/rm/readdir-bug.sh
new file mode 100755
index 0000000..01824fb
--- /dev/null
+++ b/tests/rm/readdir-bug.sh
@@ -0,0 +1,41 @@
+#!/bin/sh
+# Exercise the Darwin/MacOS bug worked around on 2006-09-29,
+# whereby rm would fail to remove all entries in a directory.
+
+# Copyright (C) 2006-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_ rm
+
+# Create a directory containing many files.
+# What counts is a combination of the number of files and
+# the lengths of their names. For details, see
+# https://lists.gnu.org/r/bug-coreutils/2006-09/msg00326.html
+mkdir b || framework_failure_
+cd b || framework_failure_
+for i in $(seq 1 250); do
+ touch $(printf %040d $i) || framework_failure_
+done
+cd .. || framework_failure_
+
+
+# On a buggy system, this would fail with the diagnostic,
+# "cannot remove directory 'b': Directory not empty"
+rm -rf b || fail=1
+
+test -d b && fail=1
+
+Exit $fail
diff --git a/tests/rm/rm-readdir-fail.sh b/tests/rm/rm-readdir-fail.sh
new file mode 100755
index 0000000..9798fa5
--- /dev/null
+++ b/tests/rm/rm-readdir-fail.sh
@@ -0,0 +1,120 @@
+#!/bin/sh
+# Test rm's behaviour when the directory cannot be read.
+# This test is skipped on systems that lack LD_PRELOAD support.
+
+# Copyright (C) 2016-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_ rm
+require_gcc_shared_
+
+mkdir -p dir/notempty || framework_failure_
+
+# Simulate "readdir" failure.
+cat > k.c <<\EOF || framework_failure_
+#define _GNU_SOURCE
+
+/* Setup so we don't have to worry about readdir64. */
+#ifndef __LP64__
+# define _FILE_OFFSET_BITS 64
+#endif
+
+#include <dlfcn.h>
+#include <dirent.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+struct dirent *readdir (DIR *dirp)
+{
+ static int count = 1;
+
+#ifndef __LP64__
+ if (count == 1)
+ fclose (fopen ("32bit", "w"));
+ errno = ENOSYS;
+ return NULL;
+#endif
+
+ static struct dirent *(*real_readdir)(DIR *dirp);
+ if (! real_readdir && ! (real_readdir = dlsym (RTLD_NEXT, "readdir")))
+ {
+ fprintf (stderr, "Failed to find readdir()\n");
+ errno = ESRCH;
+ return NULL;
+ }
+ struct dirent* d;
+ if (! (d = real_readdir (dirp)))
+ {
+ fprintf (stderr, "Failed to get dirent\n");
+ errno = ENOENT;
+ return NULL;
+ }
+
+ /* Flag that LD_PRELOAD and above functions work. */
+ if (count == 1)
+ fclose (fopen ("preloaded", "w"));
+
+ /* Return some entries to trigger partial read failure,
+ ensuring we don't return ignored '.' or '..' */
+ char const *readdir_partial = getenv ("READDIR_PARTIAL");
+ if (readdir_partial && *readdir_partial && count <= 3)
+ {
+ count++;
+ d->d_name[0]='0'+count; d->d_name[1]='\0';
+#ifdef _DIRENT_HAVE_D_NAMLEN
+ d->d_namlen = 2;
+#endif
+ errno = 0;
+ return d;
+ };
+
+ /* Fail. */
+ errno = ENOENT;
+ return NULL;
+}
+EOF
+
+# Then compile/link it:
+gcc_shared_ k.c k.so \
+ || framework_failure_ 'failed to build shared library'
+
+# Test if LD_PRELOAD works:
+export READDIR_PARTIAL
+for READDIR_PARTIAL in '' '1'; do
+ rm -f preloaded
+ (export LD_PRELOAD=$LD_PRELOAD:./k.so
+ returns_ 1 rm -Rf dir 2>>errt) || fail=1
+ if test -f 32bit; then
+ skip_ 'This test only supports 64 bit systems'
+ elif ! test -f preloaded; then
+ cat errt
+ skip_ "internal test failure: maybe LD_PRELOAD doesn't work?"
+ fi
+done
+
+# First case is failure to read any items from dir, then assume empty.
+# Generally that will be diagnosed when rm tries to rmdir().
+# Second case is more general error where we fail immediately
+# (with ENOENT in this case but it could be anything).
+cat <<EOF > exp
+rm: cannot remove 'dir'
+rm: traversal failed: dir
+EOF
+sed 's/\(rm:.*\):.*/\1/' errt > err || framework_failure_
+compare exp err || fail=1
+
+Exit $fail
diff --git a/tests/rm/rm1.sh b/tests/rm/rm1.sh
new file mode 100755
index 0000000..4636cf2
--- /dev/null
+++ b/tests/rm/rm1.sh
@@ -0,0 +1,48 @@
+#!/bin/sh
+# exercise another small part of remove.c
+
+# Copyright (C) 2002-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_ rm
+skip_if_root_
+
+mkdir -p b/a/p b/c b/d || framework_failure_
+chmod ug-w b/a || framework_failure_
+
+
+# This should fail.
+rm -rf b > out 2>&1 && fail=1
+cat <<\EOF > exp
+rm: cannot remove directory 'b/a/p': Permission denied
+EOF
+
+# On some systems, rm doesn't have enough information to
+# say it's a directory.
+cat <<\EOF > exp2
+rm: cannot remove 'b/a/p': Permission denied
+EOF
+
+cmp out exp > /dev/null 2>&1 || {
+ cmp out exp2 || fail=1
+ }
+test $fail = 1 && compare exp out
+
+test -d b/a/p || fail=1
+test -d b/c && fail=1
+test -d b/d && fail=1
+
+Exit $fail
diff --git a/tests/rm/rm2.sh b/tests/rm/rm2.sh
new file mode 100755
index 0000000..a69fcdb
--- /dev/null
+++ b/tests/rm/rm2.sh
@@ -0,0 +1,55 @@
+#!/bin/sh
+# exercise another small part of remove.c
+
+# Copyright (C) 2002-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_ rm
+skip_if_root_
+
+mkdir -p a/0 || framework_failure_
+mkdir -p a/1/2 b/3 || framework_failure_
+mkdir a/2 a/3 || framework_failure_
+chmod u-x a/1 b || framework_failure_
+
+
+# Exercise two separate code paths -- though both result
+# in the same sort of diagnostic.
+# Both of these should fail.
+rm -rf a b > out 2>&1 && fail=1
+cat <<\EOF > exp
+rm: cannot remove 'a/1': Permission denied
+rm: cannot remove 'b': Permission denied
+EOF
+
+cat <<\EOF > exp-solaris
+rm: cannot remove 'a/1/2': Permission denied
+rm: cannot remove 'b/3': Permission denied
+EOF
+
+cmp out exp > /dev/null 2>&1 \
+ || { cmp out exp-solaris > /dev/null 2>&1 || fail=1; }
+test $fail = 1 && compare exp out
+
+test -d a/0 && fail=1
+test -d a/1 || fail=1
+test -d a/2 && fail=1
+test -d a/3 && fail=1
+
+chmod u+x b
+test -d b/3 || fail=1
+
+Exit $fail
diff --git a/tests/rm/rm3.sh b/tests/rm/rm3.sh
new file mode 100755
index 0000000..c62ae3e
--- /dev/null
+++ b/tests/rm/rm3.sh
@@ -0,0 +1,73 @@
+#!/bin/sh
+# exercise another small part of remove.c
+
+# Copyright (C) 2002-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_ rm
+skip_if_root_
+
+mkdir -p z || framework_failure_
+cd z || framework_failure_
+touch empty empty-u || framework_failure_
+echo not-empty > fu
+ln -s empty-f slink
+ln -s . slinkdot
+mkdir d du || framework_failure_
+chmod u-w fu du empty-u || framework_failure_
+cd ..
+
+
+cat <<EOF > in || framework_failure_
+y
+y
+y
+y
+y
+y
+y
+y
+y
+EOF
+
+# Both of these should fail.
+rm -ir z < in > out 2>&1 || fail=1
+
+# Given input like 'rm: ...? rm: ...? ' (no trailing newline),
+# the 'head...' part of the pipeline below removes the trailing space, so
+# that sed doesn't have to deal with a line lacking a terminating newline.
+# This avoids a bug whereby some vendor-provided (Tru64) versions of sed
+# would mistakenly tack a newline onto the end of the output.
+tr '?' '\n' < out | head --bytes=-1 | sed 's/^ //' |sort > o2
+mv o2 out || framework_failure_
+
+sort <<EOF > exp || framework_failure_
+rm: descend into directory 'z'
+rm: remove regular empty file 'z/empty'
+rm: remove write-protected regular file 'z/fu'
+rm: remove write-protected regular empty file 'z/empty-u'
+rm: remove symbolic link 'z/slink'
+rm: remove symbolic link 'z/slinkdot'
+rm: remove directory 'z/d'
+rm: remove write-protected directory 'z/du'
+rm: remove directory 'z'
+EOF
+
+compare exp out || fail=1
+
+test -d z && fail=1
+
+Exit $fail
diff --git a/tests/rm/rm4.sh b/tests/rm/rm4.sh
new file mode 100755
index 0000000..6c96df7
--- /dev/null
+++ b/tests/rm/rm4.sh
@@ -0,0 +1,31 @@
+#!/bin/sh
+# ensure that 'rm dir' fails without --recursive
+
+# Copyright (C) 2002-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_ rm
+skip_if_root_
+
+mkdir dir || framework_failure_
+
+
+# This should fail.
+returns_ 1 rm dir > /dev/null 2>&1 || fail=1
+
+test -d dir || fail=1
+
+Exit $fail
diff --git a/tests/rm/rm5.sh b/tests/rm/rm5.sh
new file mode 100755
index 0000000..5ca5a4b
--- /dev/null
+++ b/tests/rm/rm5.sh
@@ -0,0 +1,52 @@
+#!/bin/sh
+# a basic test of rm -ri
+
+# Copyright (C) 2002-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_ rm
+skip_if_root_
+
+mkdir -p d/e || framework_failure_
+cat <<EOF > in || framework_failure_
+y
+y
+y
+EOF
+
+cat <<\EOF > exp || framework_failure_
+rm: descend into directory 'd'
+rm: remove directory 'd/e'
+rm: remove directory 'd'
+EOF
+
+
+rm -ir d < in > out 2>&1 || fail=1
+
+# Given input like 'rm: ...? rm: ...? ' (no trailing newline),
+# the 'head...' part of the pipeline below removes the trailing space, so
+# that sed doesn't have to deal with a line lacking a terminating newline.
+# This avoids a bug whereby some vendor-provided (Tru64) versions of sed
+# would mistakenly tack a newline onto the end of the output.
+tr '?' '\n' < out | head --bytes=-1 | sed 's/^ //' > o2
+mv o2 out
+
+# Make sure it's been removed.
+test -d d && fail=1
+
+compare exp out || fail=1
+
+Exit $fail
diff --git a/tests/rm/sunos-1.sh b/tests/rm/sunos-1.sh
new file mode 100755
index 0000000..c896273
--- /dev/null
+++ b/tests/rm/sunos-1.sh
@@ -0,0 +1,29 @@
+#!/bin/sh
+# Make sure that rm -r '' fails.
+
+# Copyright (C) 1997-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/>.
+
+
+# On SunOS 4.1.3, running rm -r '' in a nonempty directory may
+# actually remove files with names of entries in the current directory
+# but relative to '/' rather than relative to the current directory.
+
+. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src
+print_ver_ rm
+
+returns_ 1 rm -r '' > /dev/null 2>&1 || fail=1
+
+Exit $fail
diff --git a/tests/rm/unread2.sh b/tests/rm/unread2.sh
new file mode 100755
index 0000000..3ffeb97
--- /dev/null
+++ b/tests/rm/unread2.sh
@@ -0,0 +1,35 @@
+#!/bin/sh
+# exercise one small part of remove.c
+
+# Copyright (C) 2002-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_ rm
+skip_if_root_
+
+mkdir -p a/b || framework_failure_
+chmod u-r a
+
+
+# This should fail.
+rm -rf a > out 2>&1 && fail=1
+cat <<\EOF > exp
+rm: cannot remove 'a': Permission denied
+EOF
+
+compare exp out || fail=1
+
+Exit $fail
diff --git a/tests/rm/unread3.sh b/tests/rm/unread3.sh
new file mode 100755
index 0000000..ce3e268
--- /dev/null
+++ b/tests/rm/unread3.sh
@@ -0,0 +1,43 @@
+#!/bin/sh
+# Ensure that rm works even from an unreadable working directory.
+
+# Copyright (C) 2004-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_ rm
+skip_if_root_
+
+mkdir -p a/1 b c d/2 e/3 || framework_failure_
+
+
+t=$(pwd)
+cd c
+chmod u=x,go= .
+
+# With coreutils-5.2.1, this would get a failed assertion.
+rm -r "$t/a" "$t/b" || fail=1
+
+# With coreutils-5.2.1, this would get the following:
+# rm: cannot get current directory: Permission denied
+# rm: failed to return to initial working directory: Bad file descriptor
+rm -r "$t/d" "$t/e" || fail=1
+
+test -d "$t/a" && fail=1
+test -d "$t/b" && fail=1
+test -d "$t/d" && fail=1
+test -d "$t/e" && fail=1
+
+Exit $fail
diff --git a/tests/rm/unreadable.pl b/tests/rm/unreadable.pl
new file mode 100755
index 0000000..6a606ce
--- /dev/null
+++ b/tests/rm/unreadable.pl
@@ -0,0 +1,50 @@
+#!/usr/bin/perl
+# Test "rm" and unreadable directories.
+
+# Copyright (C) 1998-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/>.
+
+use strict;
+
+(my $program_name = $0) =~ s|.*/||;
+
+# Turn off localization of executable's output.
+@ENV{qw(LANGUAGE LANG LC_ALL)} = ('C') x 3;
+
+my $d = "dir-$$";
+my $mkdir = {PRE => sub {mkdir $d,0100 or die "$d: $!\n"}};
+my $prog = 'rm';
+my $uid = $<;
+
+my @Tests =
+ (
+ # test-name options input expected-output
+ #
+ # Removing an empty, unwritable directory succeeds.
+ ['unreadable-1', '-rf', $d, {EXIT => 0}, $mkdir],
+
+ ['unreadable-2', '-rf', $d,
+ {EXIT => $uid == 0 ? 0 : 1},
+ {ERR => $uid == 0 ? ''
+ : "$prog: cannot remove '$d': Permission denied\n"},
+ {PRE => sub { (mkdir $d,0700 and mkdir "$d/x",0700 and chmod 0100,$d)
+ or die "$d: $!\n"}} ],
+ );
+
+my $save_temps = $ENV{SAVE_TEMPS};
+my $verbose = $ENV{VERBOSE};
+
+my $fail = run_tests ($program_name, $prog, \@Tests, $save_temps, $verbose);
+exit $fail;
diff --git a/tests/rm/v-slash.sh b/tests/rm/v-slash.sh
new file mode 100755
index 0000000..0cdd759
--- /dev/null
+++ b/tests/rm/v-slash.sh
@@ -0,0 +1,34 @@
+#!/bin/sh
+# avoid extra slashes in --verbose output
+
+# Copyright (C) 2007-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_ rm
+
+mkdir a || framework_failure_
+touch a/x || framework_failure_
+
+
+rm --verbose -r a/// > out || fail=1
+cat <<\EOF > exp || framework_failure_
+removed 'a/x'
+removed directory 'a/'
+EOF
+
+compare exp out || fail=1
+
+Exit $fail