bug-coreutils
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

new option: rm --one-file-system


From: Jim Meyering
Subject: new option: rm --one-file-system
Date: Tue, 24 Oct 2006 21:55:05 +0200

The patch below gives rm a new option, --one-file-system, that works a lot
like du's option by the same name.  With it, "rm -fr --one-file-system
..." works a lot like "find ... -xdev -depth -delete", but the former
is slightly more accessible.

Note the addition of a root-requiring test that
actually creates a bind mount.

By way of justification, here's what I've added to the
texinfo documentation:
---
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.

Here are the diffs:

[ChangeLog]
* 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/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.
diff --git a/NEWS b/NEWS
index 41a418a..734d57e 100644
--- a/NEWS
+++ b/NEWS
@@ -2,6 +2,10 @@ GNU coreutils NEWS

 * 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/coreutils.texi b/doc/coreutils.texi
index 3a180f6..db9d60d 100644
--- a/doc/coreutils.texi
+++ b/doc/coreutils.texi
@@ -7772,6 +7772,24 @@ removal is requested.  Equivalent to @op
 Specifying @option{--interactive} and no @var{when} is equivalent to
 @option{--interactive=always}.

address@hidden --one-file-system
address@hidden --one-file-system
address@hidden 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
+silently 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 299a6ac..03e96e5 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 d362db0..add85dd 100644
--- a/src/remove.c
+++ b/src/remove.c
@@ -1298,6 +1298,7 @@ remove_dir (int fd_cwd, Dirstack_state *
            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 *
        }
       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 d3609d7..2dc6176 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 28e09ce..0c93a04 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 b13294b..d173ecd 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 d17151d..927bac8 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 b29f0e8..f570656 100755
--- a/tests/mv/acl
+++ b/tests/mv/acl
@@ -20,7 +20,7 @@ # Foundation, Inc., 51 Franklin Street,
 # 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 00ecc10..8d5c69d 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 dc345f1..7ce9176 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 f5b7f74..11fddf2 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 5e5bb1a..1592e0c 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 dadcd69..4d7c697 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 2bbd9d2..e4c5dc9 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 9a8eac7..66cfe0d 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 a4b3610..b8c03e0 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 eb66779..5cd198c 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 f510edf..969dc74 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 864fef6..6473cfb 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
similarity index 100%
rename from tests/mv/setup
rename to tests/other-fs-tmpdir
diff --git a/tests/rm/one-file-system b/tests/rm/one-file-system
new file mode 100755
index 0000000..bb7ffeb
--- /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




reply via email to

[Prev in Thread] Current Thread [Next in Thread]