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.
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
* 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]
+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
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
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. */
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
}
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. */
/* 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;
enum
{
INTERACTIVE_OPTION = CHAR_MAX + 1,
+ ONE_FILE_SYSTEM,
NO_PRESERVE_ROOT,
PRESERVE_ROOT,
PRESUME_INPUT_TTY_OPTION
{"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},
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\
{
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);
break;
}
+ case ONE_FILE_SYSTEM:
+ x.one_file_system = true;
+ break;
+
case NO_PRESERVE_ROOT:
preserve_root = false;
break;
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
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
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
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" \
# 02110-1301, USA.
. $srcdir/../acl
-. $srcdir/setup
+. $srcdir/../other-fs-tmpdir
# Make sure we get English translations.
. $srcdir/../lang-default
mv --version
fi
-. $srcdir/setup
+. $srcdir/../other-fs-tmpdir
. $srcdir/../envvar-check
if test -z "$other_partition_tmpdir"; then
mv --version
fi
-. $srcdir/setup
+. $srcdir/../other-fs-tmpdir
# Make sure we get English translations.
. $srcdir/../lang-default
mv --version
fi
-. $srcdir/setup
+. $srcdir/../other-fs-tmpdir
. $srcdir/../envvar-check
if test -z "$other_partition_tmpdir"; then
mv --version
fi
-. $srcdir/setup
+. $srcdir/../other-fs-tmpdir
. $srcdir/../envvar-check
PRIV_CHECK_ARG=require-non-root . $srcdir/../priv-check
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
mv --version
fi
-. $srcdir/setup
+. $srcdir/../other-fs-tmpdir
. $srcdir/../envvar-check
. $srcdir/../lang-default
PRIV_CHECK_ARG=require-non-root . $srcdir/../priv-check
mv --version
fi
-. $srcdir/setup
+. $srcdir/../other-fs-tmpdir
. $srcdir/../envvar-check
pwd=`pwd`
mv --version
fi
-. $srcdir/setup
+. $srcdir/../other-fs-tmpdir
. $srcdir/../envvar-check
pwd=`pwd`
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
mv --version
fi
-. $srcdir/setup
+. $srcdir/../other-fs-tmpdir
. $srcdir/../envvar-check
. $srcdir/../lang-default
mv --version
fi
-. $srcdir/setup
+. $srcdir/../other-fs-tmpdir
. $srcdir/../envvar-check
if test -z "$other_partition_tmpdir"; then
AUTOMAKE_OPTIONS = 1.1 gnits
TESTS = \
+ one-file-system \
ignorable \
readdir-bug \
empty-inacc \
--- /dev/null
+#!/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