new feature: rm accepts new option: --one-file-system
authorJim Meyering <jim@meyering.net>
Tue, 24 Oct 2006 22:01:33 +0000 (00:01 +0200)
committerJim Meyering <jim@meyering.net>
Tue, 24 Oct 2006 22:01:33 +0000 (00:01 +0200)
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.

25 files changed:
ChangeLog
NEWS
doc/ChangeLog
doc/coreutils.texi
src/mv.c
src/remove.c
src/remove.h
src/rm.c
tests/Makefile.am
tests/mv/Makefile.am
tests/mv/acl
tests/mv/backup-is-src
tests/mv/hard-link-1
tests/mv/into-self-2
tests/mv/leak-fd
tests/mv/mv-special-1
tests/mv/part-fail
tests/mv/part-hardlink
tests/mv/part-rename
tests/mv/part-symlink
tests/mv/partition-perm
tests/mv/to-symlink
tests/other-fs-tmpdir [moved from tests/mv/setup with 100% similarity]
tests/rm/Makefile.am
tests/rm/one-file-system [new file with mode: 0755]

index eb2ac24..939e99f 100644 (file)
--- 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 41a418a..734d57e 100644 (file)
--- 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]
 
index 2867798..0e0b5c2 100644 (file)
@@ -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
index 3a180f6..ee81792 100644 (file)
@@ -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
index 299a6ac..03e96e5 100644 (file)
--- 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.  */
index d362db0..add85dd 100644 (file)
@@ -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.  */
index d3609d7..2dc6176 100644 (file)
@@ -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;
 
index 28e09ce..0c93a04 100644 (file)
--- 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;
index b13294b..d173ecd 100644 (file)
@@ -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
 
index d17151d..927bac8 100644 (file)
@@ -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" \
index b29f0e8..f570656 100755 (executable)
@@ -20,7 +20,7 @@
 # 02110-1301, USA.
 
 . $srcdir/../acl
-. $srcdir/setup
+. $srcdir/../other-fs-tmpdir
 # Make sure we get English translations.
 . $srcdir/../lang-default
 
index 00ecc10..8d5c69d 100755 (executable)
@@ -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
index dc345f1..7ce9176 100755 (executable)
@@ -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
 
index f5b7f74..11fddf2 100755 (executable)
@@ -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
index 5e5bb1a..1592e0c 100755 (executable)
@@ -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
 
index dadcd69..4d7c697 100755 (executable)
@@ -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
index 2bbd9d2..e4c5dc9 100755 (executable)
@@ -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
index 9a8eac7..66cfe0d 100755 (executable)
@@ -26,7 +26,7 @@ if test "$VERBOSE" = yes; then
   mv --version
 fi
 
-. $srcdir/setup
+. $srcdir/../other-fs-tmpdir
 . $srcdir/../envvar-check
 
 pwd=`pwd`
index a4b3610..b8c03e0 100755 (executable)
@@ -25,7 +25,7 @@ if test "$VERBOSE" = yes; then
   mv --version
 fi
 
-. $srcdir/setup
+. $srcdir/../other-fs-tmpdir
 . $srcdir/../envvar-check
 
 pwd=`pwd`
index eb66779..5cd198c 100755 (executable)
@@ -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
index f510edf..969dc74 100755 (executable)
@@ -23,7 +23,7 @@ if test "$VERBOSE" = yes; then
   mv --version
 fi
 
-. $srcdir/setup
+. $srcdir/../other-fs-tmpdir
 . $srcdir/../envvar-check
 . $srcdir/../lang-default
 
index 864fef6..6473cfb 100755 (executable)
@@ -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
similarity index 100%
rename from tests/mv/setup
rename to tests/other-fs-tmpdir
index ada461b..6670307 100644 (file)
@@ -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 (executable)
index 0000000..bb7ffeb
--- /dev/null
@@ -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