summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJim Meyering <jim@meyering.net>2006-10-25 00:01:33 +0200
committerJim Meyering <jim@meyering.net>2006-10-25 00:01:33 +0200
commit5e42576c017905627e209acc20bf6e6abd1db6ef (patch)
treedec84f1b293d51189d9ab440710d8ca16bc91c4c
parentba6b1acefd8c9d9a4c506806d68e891879706a2c (diff)
downloadcoreutils-5e42576c017905627e209acc20bf6e6abd1db6ef.tar.xz
new feature: rm accepts new option: --one-file-system
Suggested by Steve McIntyre in <http://bugs.debian.org/392925>. * src/remove.h (struct rm_options) [one_file_system]: New member. * src/rm.c (rm_option_init): Initialize it. (usage): Document the option. * src/mv.c (rm_option_init): Likewise. * src/remove.c (remove_dir): With --one-file-system and --recursive, for each directory command line argument, do not affect a file system different from that of the starting directory. And give a diagnostic. * src/rm.c (ONE_FILE_SYSTEM): New enum. (main): Handle new option. * tests/rm/one-file-system: Test the above. * tests/rm/Makefile.am (TESTS): Add one-file-system. * tests/Makefile.am (check-root): Add the rm/one-file-system test to the list. (EXTRA_DIST): Add other-fs-tmpdir. * tests/mv/setup: Removed. Renamed to... * tests/other-fs-tmpdir: ...this new file. * tests/mv/Makefile.am (EXTRA_DIST): Remove setup. * tests/mv/acl: Reflect renaming: use ../other-fs-tmpdir. * tests/mv/backup-is-src: Likewise. * tests/mv/hard-link-1: Likewise. * tests/mv/leak-fd: Likewise. * tests/mv/mv-special-1: Likewise. * tests/mv/part-fail: Likewise. * tests/mv/part-hardlink: Likewise. * tests/mv/part-rename: Likewise. * tests/mv/part-symlink: Likewise. * tests/mv/partition-perm: Likewise. * tests/mv/to-symlink: Likewise. * tests/mv/into-self-2: Likewise. [doc/ChangeLog] * coreutils.texi (rm invocation): Describe --one-file-system.
-rw-r--r--ChangeLog33
-rw-r--r--NEWS4
-rw-r--r--doc/ChangeLog4
-rw-r--r--doc/coreutils.texi18
-rw-r--r--src/mv.c1
-rw-r--r--src/remove.c25
-rw-r--r--src/remove.h8
-rw-r--r--src/rm.c12
-rw-r--r--tests/Makefile.am10
-rw-r--r--tests/mv/Makefile.am2
-rwxr-xr-xtests/mv/acl2
-rwxr-xr-xtests/mv/backup-is-src2
-rwxr-xr-xtests/mv/hard-link-12
-rwxr-xr-xtests/mv/into-self-22
-rwxr-xr-xtests/mv/leak-fd2
-rwxr-xr-xtests/mv/mv-special-12
-rwxr-xr-xtests/mv/part-fail2
-rwxr-xr-xtests/mv/part-hardlink2
-rwxr-xr-xtests/mv/part-rename2
-rwxr-xr-xtests/mv/part-symlink2
-rwxr-xr-xtests/mv/partition-perm2
-rwxr-xr-xtests/mv/to-symlink2
-rwxr-xr-xtests/other-fs-tmpdir (renamed from tests/mv/setup)0
-rw-r--r--tests/rm/Makefile.am1
-rwxr-xr-xtests/rm/one-file-system67
25 files changed, 189 insertions, 20 deletions
diff --git a/ChangeLog b/ChangeLog
index eb2ac2491..939e99f43 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,38 @@
2006-10-24 Jim Meyering <jim@meyering.net>
+ * NEWS: new feature: rm accepts new option: --one-file-system
+ Suggested by Steve McIntyre in <http://bugs.debian.org/392925>.
+ * src/remove.h (struct rm_options) [one_file_system]: New member.
+ * src/rm.c (rm_option_init): Initialize it.
+ (usage): Document the option.
+ * src/mv.c (rm_option_init): Likewise.
+ * src/remove.c (remove_dir): With --one-file-system and --recursive,
+ for each directory command line argument, do not affect a file system
+ different from that of the starting directory. And give a diagnostic.
+ * src/rm.c (ONE_FILE_SYSTEM): New enum.
+ (main): Handle new option.
+ * tests/rm/one-file-system: Test the above.
+ * tests/rm/Makefile.am (TESTS): Add one-file-system.
+ * tests/Makefile.am (check-root): Add the rm/one-file-system
+ test to the list.
+ (EXTRA_DIST): Add other-fs-tmpdir.
+
+ * tests/mv/setup: Removed. Renamed to...
+ * tests/other-fs-tmpdir: ...this new file.
+ * tests/mv/Makefile.am (EXTRA_DIST): Remove setup.
+ * tests/mv/acl: Reflect renaming: use ../other-fs-tmpdir.
+ * tests/mv/backup-is-src: Likewise.
+ * tests/mv/hard-link-1: Likewise.
+ * tests/mv/leak-fd: Likewise.
+ * tests/mv/mv-special-1: Likewise.
+ * tests/mv/part-fail: Likewise.
+ * tests/mv/part-hardlink: Likewise.
+ * tests/mv/part-rename: Likewise.
+ * tests/mv/part-symlink: Likewise.
+ * tests/mv/partition-perm: Likewise.
+ * tests/mv/to-symlink: Likewise.
+ * tests/mv/into-self-2: Likewise.
+
Don't let a failure in one test stop "make -k" from running the others.
* tests/Makefile.am (t1 t2 t3 t4 t5): New targets.
(check-root): Depend on them, rather than executing the five
diff --git a/NEWS b/NEWS
index 41a418acb..734d57ee6 100644
--- a/NEWS
+++ b/NEWS
@@ -2,6 +2,10 @@ GNU coreutils NEWS -*- outline -*-
* Major changes in release 6.5-cvs (2006-??-??)
+** New features
+
+ rm accepts a new option: --one-file-system
+
* Major changes in release 6.4 (2006-10-22) [stable]
diff --git a/doc/ChangeLog b/doc/ChangeLog
index 2867798b0..0e0b5c2a0 100644
--- a/doc/ChangeLog
+++ b/doc/ChangeLog
@@ -1,3 +1,7 @@
+2006-10-23 Jim Meyering <jim@meyering.net>
+
+ * coreutils.texi (rm invocation): Describe --one-file-system.
+
2006-09-26 Paul Eggert <eggert@cs.ucla.edu>
* coreutils.texi (groups invocation): "groups" no longer prefixes
diff --git a/doc/coreutils.texi b/doc/coreutils.texi
index 3a180f669..ee8179246 100644
--- a/doc/coreutils.texi
+++ b/doc/coreutils.texi
@@ -7772,6 +7772,24 @@ removal is requested. Equivalent to @option{-I}.
Specifying @option{--interactive} and no @var{when} is equivalent to
@option{--interactive=always}.
+@itemx --one-file-system
+@opindex --one-file-system
+@cindex one file system, restricting @command{rm} to
+When removing a hierarchy recursively, skip any directory that is on a
+file system different from that of the corresponding command line argument.
+
+This option is useful when removing a build ``chroot'' hierarchy,
+which normally contains no valuable data. However, it is not uncommon
+to bind-mount @file{/home} into such a hierarchy, to make it easier to
+use one's start-up file. The catch is that it's easy to forget to
+unmount @file{/home}. Then, when you use @command{rm -rf} to remove
+your normally throw-away chroot, that command will remove everything
+under @file{/home}, too.
+Use the @option{--one-file-system} option, and it will
+warn about and skip directories on other file systems.
+Of course, this will not save your @file{/home} if it and your
+chroot happen to be on the same file system.
+
@itemx --preserve-root
@opindex --preserve-root
@cindex root directory, disallow recursive destruction
diff --git a/src/mv.c b/src/mv.c
index 299a6acfd..03e96e5a0 100644
--- a/src/mv.c
+++ b/src/mv.c
@@ -94,6 +94,7 @@ rm_option_init (struct rm_options *x)
x->ignore_missing_files = false;
x->root_dev_ino = NULL;
x->recursive = true;
+ x->one_file_system = false;
/* Should we prompt for removal, too? No. Prompting for the `move'
part is enough. It implies removal. */
diff --git a/src/remove.c b/src/remove.c
index d362db0ee..add85dd7b 100644
--- a/src/remove.c
+++ b/src/remove.c
@@ -1298,6 +1298,7 @@ remove_dir (int fd_cwd, Dirstack_state *ds, char const *dir,
struct rm_options const *x, int *cwd_errno)
{
enum RM_status status;
+ dev_t current_dev = dir_st->st_dev;
/* There is a race condition in that an attacker could replace the nonempty
directory, DIR, with a symlink between the preceding call to rmdir
@@ -1359,15 +1360,31 @@ remove_dir (int fd_cwd, Dirstack_state *ds, char const *dir,
}
if (subdir)
{
- AD_push (dirfd (dirp), ds, subdir, &subdir_sb);
- AD_INIT_OTHER_MEMBERS ();
+ if ( ! x->one_file_system
+ || subdir_sb.st_dev == current_dev)
+ {
+ AD_push (dirfd (dirp), ds, subdir, &subdir_sb);
+ AD_INIT_OTHER_MEMBERS ();
+ free (subdir);
+ continue;
+ }
+ /* Here, --one-file-system is in effect, and with remove_cwd_entries'
+ traversal into the current directory, (known as SUBDIR, from ..),
+ DIRP's device number is different from CURRENT_DEV. Arrange not
+ to do anything more with this hierarchy. */
+ error (0, errno, _("skipping %s, since it's on a different device"),
+ quote (full_filename (subdir)));
free (subdir);
- continue;
+ AD_mark_current_as_unremovable (ds);
+ tmp_status = RM_ERROR;
+ UPDATE_STATUS (status, tmp_status);
}
/* Execution reaches this point when we've removed the last
- removable entry from the current directory. */
+ removable entry from the current directory -- or, with
+ --one-file-system, when the current directory is on a
+ different file system. */
{
/* The name of the directory that we have just processed,
nominally removing all of its contents. */
diff --git a/src/remove.h b/src/remove.h
index d3609d768..2dc617618 100644
--- a/src/remove.h
+++ b/src/remove.h
@@ -30,6 +30,14 @@ struct rm_options
/* If true, query the user about whether to remove each file. */
bool interactive;
+ /* If true, do not traverse into (or remove) any directory that is
+ on a file system (i.e., that has a different device number) other
+ than that of the corresponding command line argument. Note that
+ even without this option, rm will fail in the end, due to its
+ probable inability to remove the mount point. But there, the
+ diagnostic comes too late -- after removing all contents. */
+ bool one_file_system;
+
/* If true, recursively remove directories. */
bool recursive;
diff --git a/src/rm.c b/src/rm.c
index 28e09ce12..0c93a040a 100644
--- a/src/rm.c
+++ b/src/rm.c
@@ -72,6 +72,7 @@ char *program_name;
enum
{
INTERACTIVE_OPTION = CHAR_MAX + 1,
+ ONE_FILE_SYSTEM,
NO_PRESERVE_ROOT,
PRESERVE_ROOT,
PRESUME_INPUT_TTY_OPTION
@@ -90,6 +91,7 @@ static struct option const long_opts[] =
{"force", no_argument, NULL, 'f'},
{"interactive", optional_argument, NULL, INTERACTIVE_OPTION},
+ {"one-file-system", no_argument, NULL, ONE_FILE_SYSTEM},
{"no-preserve-root", no_argument, NULL, NO_PRESERVE_ROOT},
{"preserve-root", no_argument, NULL, PRESERVE_ROOT},
@@ -170,6 +172,11 @@ Remove (unlink) the FILE(s).\n\
always (-i). Without WHEN, prompt always\n\
"), stdout);
fputs (_("\
+ --one-file-system when removing a hierarchy recursively, skip any\n\
+ directory that is on a file system different from\n\
+ that of the corresponding command line argument\n\
+"), stdout);
+ fputs (_("\
--no-preserve-root do not treat `/' specially\n\
--preserve-root do not remove `/' (default)\n\
-r, -R, --recursive remove directories and their contents recursively\n\
@@ -207,6 +214,7 @@ rm_option_init (struct rm_options *x)
{
x->ignore_missing_files = false;
x->interactive = false;
+ x->one_file_system = false;
x->recursive = false;
x->root_dev_ino = NULL;
x->stdin_tty = isatty (STDIN_FILENO);
@@ -299,6 +307,10 @@ main (int argc, char **argv)
break;
}
+ case ONE_FILE_SYSTEM:
+ x.one_file_system = true;
+ break;
+
case NO_PRESERVE_ROOT:
preserve_root = false;
break;
diff --git a/tests/Makefile.am b/tests/Makefile.am
index b13294bf5..d173ecda9 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -15,7 +15,8 @@ TESTS_ENVIRONMENT = \
EXTRA_DIST = \
$(TESTS) Coreutils.pm Makefile.am.in README acl envvar-check \
- expensive group-names input-tty lang-default mk-script priv-check \
+ expensive group-names input-tty lang-default mk-script \
+ other-fs-tmpdir priv-check \
rwx-to-mode sample-test setgid-check sparse-file \
umask-check very-expensive
@@ -28,8 +29,9 @@ SUBDIRS = \
tsort unexpand uniq wc
## N O T E :: Please do not add new directories.
-.PHONY: check-root t1 t2 t3 t4 t5
-check-root: t1 t2 t3 t4 t5
+all_t = t1 t2 t3 t4 t5 t6
+.PHONY: check-root $(all_t)
+check-root: $(all_t)
t1:
cd chown && $(MAKE) check TESTS=basic
@@ -41,6 +43,8 @@ t4:
cd rm && $(MAKE) check TESTS=fail-2eperm
t5:
cd tail-2 && $(MAKE) check TESTS=append-only
+t6:
+ cd rm && $(MAKE) check TESTS=one-file-system
check-recursive: root-hint
diff --git a/tests/mv/Makefile.am b/tests/mv/Makefile.am
index d17151de0..927bac89b 100644
--- a/tests/mv/Makefile.am
+++ b/tests/mv/Makefile.am
@@ -43,7 +43,7 @@ TESTS = \
i-1 hard-link-1 force partition-perm to-symlink dir-file diag \
part-symlink part-rename trailing-slash
-EXTRA_DIST = $(TESTS) setup vfat
+EXTRA_DIST = $(TESTS) vfat
TESTS_ENVIRONMENT = \
PERL="$(PERL)" \
PATH="$(VG_PATH_PREFIX)`pwd`/../../src$(PATH_SEPARATOR)$$PATH" \
diff --git a/tests/mv/acl b/tests/mv/acl
index b29f0e8ea..f570656fa 100755
--- a/tests/mv/acl
+++ b/tests/mv/acl
@@ -20,7 +20,7 @@
# 02110-1301, USA.
. $srcdir/../acl
-. $srcdir/setup
+. $srcdir/../other-fs-tmpdir
# Make sure we get English translations.
. $srcdir/../lang-default
diff --git a/tests/mv/backup-is-src b/tests/mv/backup-is-src
index 00ecc1033..8d5c69d6e 100755
--- a/tests/mv/backup-is-src
+++ b/tests/mv/backup-is-src
@@ -23,7 +23,7 @@ if test "$VERBOSE" = yes; then
mv --version
fi
-. $srcdir/setup
+. $srcdir/../other-fs-tmpdir
. $srcdir/../envvar-check
if test -z "$other_partition_tmpdir"; then
diff --git a/tests/mv/hard-link-1 b/tests/mv/hard-link-1
index dc345f110..7ce9176e1 100755
--- a/tests/mv/hard-link-1
+++ b/tests/mv/hard-link-1
@@ -24,7 +24,7 @@ if test "$VERBOSE" = yes; then
mv --version
fi
-. $srcdir/setup
+. $srcdir/../other-fs-tmpdir
# Make sure we get English translations.
. $srcdir/../lang-default
diff --git a/tests/mv/into-self-2 b/tests/mv/into-self-2
index f5b7f74f8..11fddf210 100755
--- a/tests/mv/into-self-2
+++ b/tests/mv/into-self-2
@@ -25,7 +25,7 @@ if test "$VERBOSE" = yes; then
mv --version
fi
-. $srcdir/setup
+. $srcdir/../other-fs-tmpdir
. $srcdir/../envvar-check
if test -z "$other_partition_tmpdir"; then
diff --git a/tests/mv/leak-fd b/tests/mv/leak-fd
index 5e5bb1a83..1592e0c50 100755
--- a/tests/mv/leak-fd
+++ b/tests/mv/leak-fd
@@ -30,7 +30,7 @@ if test "$VERBOSE" = yes; then
mv --version
fi
-. $srcdir/setup
+. $srcdir/../other-fs-tmpdir
. $srcdir/../envvar-check
PRIV_CHECK_ARG=require-non-root . $srcdir/../priv-check
diff --git a/tests/mv/mv-special-1 b/tests/mv/mv-special-1
index dadcd697d..4d7c6978c 100755
--- a/tests/mv/mv-special-1
+++ b/tests/mv/mv-special-1
@@ -29,7 +29,7 @@ tmp=mv-spec.$$
trap 'status=$?; cd "$pwd" && exec 1>&2; rm -rf $tmp $other_partition_tmpdir && exit $status' 0
trap '(exit $?); exit' 1 2 13 15
-. $srcdir/setup
+. $srcdir/../other-fs-tmpdir
. $srcdir/../envvar-check
# Make sure we get English translations.
. $srcdir/../lang-default
diff --git a/tests/mv/part-fail b/tests/mv/part-fail
index 2bbd9d247..e4c5dc96e 100755
--- a/tests/mv/part-fail
+++ b/tests/mv/part-fail
@@ -26,7 +26,7 @@ if test "$VERBOSE" = yes; then
mv --version
fi
-. $srcdir/setup
+. $srcdir/../other-fs-tmpdir
. $srcdir/../envvar-check
. $srcdir/../lang-default
PRIV_CHECK_ARG=require-non-root . $srcdir/../priv-check
diff --git a/tests/mv/part-hardlink b/tests/mv/part-hardlink
index 9a8eac770..66cfe0d4c 100755
--- a/tests/mv/part-hardlink
+++ b/tests/mv/part-hardlink
@@ -26,7 +26,7 @@ if test "$VERBOSE" = yes; then
mv --version
fi
-. $srcdir/setup
+. $srcdir/../other-fs-tmpdir
. $srcdir/../envvar-check
pwd=`pwd`
diff --git a/tests/mv/part-rename b/tests/mv/part-rename
index a4b361010..b8c03e092 100755
--- a/tests/mv/part-rename
+++ b/tests/mv/part-rename
@@ -25,7 +25,7 @@ if test "$VERBOSE" = yes; then
mv --version
fi
-. $srcdir/setup
+. $srcdir/../other-fs-tmpdir
. $srcdir/../envvar-check
pwd=`pwd`
diff --git a/tests/mv/part-symlink b/tests/mv/part-symlink
index eb66779c6..5cd198cad 100755
--- a/tests/mv/part-symlink
+++ b/tests/mv/part-symlink
@@ -32,7 +32,7 @@ trap '(exit $?); exit' 1 2 13 15
pwd_tmp=$pwd/$tmp
-. $srcdir/setup
+. $srcdir/../other-fs-tmpdir
. $srcdir/../envvar-check
# Make sure the programs use C-locale formats/translations.
. $srcdir/../lang-default
diff --git a/tests/mv/partition-perm b/tests/mv/partition-perm
index f510edf08..969dc74cd 100755
--- a/tests/mv/partition-perm
+++ b/tests/mv/partition-perm
@@ -23,7 +23,7 @@ if test "$VERBOSE" = yes; then
mv --version
fi
-. $srcdir/setup
+. $srcdir/../other-fs-tmpdir
. $srcdir/../envvar-check
. $srcdir/../lang-default
diff --git a/tests/mv/to-symlink b/tests/mv/to-symlink
index 864fef6c7..6473cfba3 100755
--- a/tests/mv/to-symlink
+++ b/tests/mv/to-symlink
@@ -24,7 +24,7 @@ if test "$VERBOSE" = yes; then
mv --version
fi
-. $srcdir/setup
+. $srcdir/../other-fs-tmpdir
. $srcdir/../envvar-check
if test -z "$other_partition_tmpdir"; then
diff --git a/tests/mv/setup b/tests/other-fs-tmpdir
index 0886437aa..0886437aa 100755
--- a/tests/mv/setup
+++ b/tests/other-fs-tmpdir
diff --git a/tests/rm/Makefile.am b/tests/rm/Makefile.am
index ada461bc3..6670307e8 100644
--- a/tests/rm/Makefile.am
+++ b/tests/rm/Makefile.am
@@ -21,6 +21,7 @@
AUTOMAKE_OPTIONS = 1.1 gnits
TESTS = \
+ one-file-system \
ignorable \
readdir-bug \
empty-inacc \
diff --git a/tests/rm/one-file-system b/tests/rm/one-file-system
new file mode 100755
index 000000000..bb7ffeb20
--- /dev/null
+++ b/tests/rm/one-file-system
@@ -0,0 +1,67 @@
+#!/bin/sh
+# Demonstrate rm's new --one-file-system option.
+
+# Copyright (C) 2006 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 2 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, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301, USA.
+
+if test "$VERBOSE" = yes; then
+ set -x
+ rm --version
+fi
+
+PRIV_CHECK_ARG=require-root . $srcdir/../priv-check
+. $srcdir/../lang-default
+. $srcdir/../other-fs-tmpdir
+
+if test -z "$other_partition_tmpdir"; then
+ (exit 77); exit 77
+fi
+
+pwd=`pwd`
+t0=`echo "$0"|sed 's,.*/,,'`.tmp; tmp=$t0/$$
+trap 'status=$?; cd $pwd; chmod -R u+rwx $t0; rm -rf $t0 && exit $status' 0
+trap '(exit $?); exit $?' 1 2 13 15
+
+t0="$t0 $other_partition_tmpdir"
+
+framework_failure=0
+mkdir -p $tmp || framework_failure=1
+cd $tmp || framework_failure=1
+
+t=$other_partition_tmpdir
+mkdir -p a/b $t/y
+mount --bind $t a/b || framework_failure=1
+
+cat <<\EOF > exp || framework_failure=1
+rm: skipping `a/b', since it's on a different device
+EOF
+
+if test $framework_failure = 1; then
+ echo "$0: failure in testing framework" 1>&2
+ (exit 1); exit 1
+fi
+
+fail=0
+
+rm --one-file-system -rf a 2> out && fail=1
+test -d $t/y || fail=1
+umount $t
+
+cmp out exp || fail=1
+test $fail = 1 && diff out exp 2> /dev/null
+
+(exit $fail); exit $fail