]> git.ipfire.org Git - thirdparty/coreutils.git/commitdiff
rm: add --preserve-root=all to protect mounts
authorPádraig Brady <P@draigBrady.com>
Mon, 11 Jun 2018 06:02:58 +0000 (23:02 -0700)
committerPádraig Brady <P@draigBrady.com>
Thu, 21 Jun 2018 04:18:32 +0000 (21:18 -0700)
* src/remove.c (rm_fts): With the --preserve-root=all extension,
reject command line arguments that are mount points.
* src/remove.h (rm_options): Add preserve_all_root to store config.
* src/mv.c (rm_option_init): Init preserve_all_root to false.
* src/rm.c (main): Init preserve_all_root as per option.
(usage): Describe the new option.
* src/remove.c (rm_fts): Lookup the parent device id,
and reject the cli argument if a separate file system.
* tests/rm/one-file-system.sh: Add a test case.
* NEWS: Mention the new feature.

NEWS
doc/coreutils.texi
src/mv.c
src/remove.c
src/remove.h
src/rm.c
tests/rm/one-file-system.sh

diff --git a/NEWS b/NEWS
index 11cce44207f9cac5789e0fe5679f02b3e402fff1..f326ba2bb14333092f30939e62418ec57aecf3d3 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -63,6 +63,9 @@ GNU coreutils NEWS                                    -*- outline -*-
   NUL instead of a newline character.  This also disables file name escaping.
   This also applies to sha*sum and b2sum.
 
+  rm --preserve-root now supports the --preserve-root=all option to
+  reject any command line argument that is mounted to a separate file system.
+
 ** Improvements
 
   cut supports line lengths up to the max file size on 32 bit systems.
index 83d31bd3425cc6cc590f7d9aa619b0c3e2f55df9..9ac82f8875ea4d2fbcfc6e6a7dadc99d9f1436c3 100644 (file)
@@ -9818,7 +9818,6 @@ removal is requested.  Equivalent to @option{-I}.
 @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.
-
 @cindex bind mount
 This option is useful when removing a build ``chroot'' hierarchy,
 which normally contains no valuable data.  However, it is not uncommon
@@ -9831,14 +9830,18 @@ 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.
+See also @option{--preserve-root=all} to protect command line arguments
+themselves.
 
-@item --preserve-root
+@item --preserve-root [=all]
 @opindex --preserve-root
 @cindex root directory, disallow recursive destruction
 Fail upon any attempt to remove the root directory, @file{/},
 when used with the @option{--recursive} option.
 This is the default behavior.
 @xref{Treating / specially}.
+When @samp{all} is specified, reject any command line argument
+that is not on the same file system as its parent.
 
 @item --no-preserve-root
 @opindex --no-preserve-root
index edc1e733fae4ade47b3866a65a3a2df14b1aba1d..b6dd72d67ca15d87b45442b3602001cf19e9ce67 100644 (file)
--- a/src/mv.c
+++ b/src/mv.c
@@ -99,6 +99,8 @@ rm_option_init (struct rm_options *x)
       die (EXIT_FAILURE, errno, _("failed to get attributes of %s"),
            quoteaf ("/"));
   }
+
+  x->preserve_all_root = false;
 }
 
 static void
index eafb9646ec5cd806b6897d64e56c5d64150b1af1..cb43ca36a5ddc085287b2f0d5a09a90527a2eabb 100644 (file)
@@ -24,6 +24,7 @@
 #include "system.h"
 #include "error.h"
 #include "file-type.h"
+#include "filenamecat.h"
 #include "ignore-value.h"
 #include "remove.h"
 #include "root-dev-ino.h"
@@ -459,6 +460,40 @@ rm_fts (FTS *fts, FTSENT *ent, struct rm_options const *x)
               fts_skip_tree (fts, ent);
               return RM_ERROR;
             }
+
+          /* If a command line argument is a mount point and
+             --preserve-root=all is in effect, diagnose and skip it.
+             This doesn't handle "/", but that's handled above.  */
+          if (x->preserve_all_root)
+            {
+              bool failed = false;
+              char *parent = file_name_concat (ent->fts_accpath, "..", NULL);
+              struct stat statbuf;
+
+              if (!parent || lstat (parent, &statbuf))
+                {
+                  error (0, 0,
+                         _("failed to stat %s: skipping %s"),
+                         quoteaf_n (0, parent),
+                         quoteaf_n (1, ent->fts_accpath));
+                  failed = true;
+                }
+
+              free (parent);
+
+              if (failed || fts->fts_dev != statbuf.st_dev)
+                {
+                  if (! failed)
+                    {
+                      error (0, 0,
+                             _("skipping %s, since it's on a different device"),
+                             quoteaf (ent->fts_path));
+                      error (0, 0, _("and --preserve-root=all is in effect"));
+                    }
+                  fts_skip_tree (fts, ent);
+                  return RM_ERROR;
+                }
+            }
         }
 
       {
index 2ce3a164fda03942f87f427b314cf4025a6638dc..55d1bdda19f5e268b535fc7be9c88e8a00a366d1 100644 (file)
@@ -56,6 +56,10 @@ struct rm_options
      and preserving '/'.  Otherwise NULL.  */
   struct dev_ino *root_dev_ino;
 
+  /* If true, do not traverse into (or remove) any directory that is
+     the root of a file system.  I.e., a separate device.  */
+  bool preserve_all_root;
+
   /* If nonzero, stdin is a tty.  */
   bool stdin_tty;
 
index dbc0cb7312e66a6ed33fcf8e2f4d4dbcb75d2c1c..f696142c889e8cb595496d134038b1cabe04e622 100644 (file)
--- a/src/rm.c
+++ b/src/rm.c
@@ -67,7 +67,7 @@ static struct option const long_opts[] =
 
   {"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},
+  {"preserve-root", optional_argument, NULL, PRESERVE_ROOT},
 
   /* This is solely for testing.  Do not document.  */
   /* It is relatively difficult to ensure that there is a tty on stdin.
@@ -151,7 +151,11 @@ Remove (unlink) the FILE(s).\n\
 "), stdout);
       fputs (_("\
       --no-preserve-root  do not treat '/' specially\n\
-      --preserve-root   do not remove '/' (default)\n\
+      --preserve-root[=all]  do not remove '/' (default);\n\
+                              with 'all', reject any command line argument\n\
+                              on a separate device from its parent\n\
+"), stdout);
+      fputs (_("\
   -r, -R, --recursive   remove directories and their contents recursively\n\
   -d, --dir             remove empty directories\n\
   -v, --verbose         explain what is being done\n\
@@ -192,6 +196,7 @@ rm_option_init (struct rm_options *x)
   x->remove_empty_directories = false;
   x->recursive = false;
   x->root_dev_ino = NULL;
+  x->preserve_all_root = false;
   x->stdin_tty = isatty (STDIN_FILENO);
   x->verbose = false;
 
@@ -294,6 +299,17 @@ main (int argc, char **argv)
           break;
 
         case PRESERVE_ROOT:
+          if (optarg)
+            {
+              if STREQ (optarg, "all")
+                x.preserve_all_root = true;
+              else
+                {
+                  die (EXIT_FAILURE, 0,
+                       _("unrecognized --preserve-root argument: %s"),
+                       quoteaf (optarg));
+                }
+            }
           preserve_root = true;
           break;
 
index 10983314f672e06d2b2a246e93e61602850ace00..d0dbac530181d7c865ebbc53b5f570a68f31ee8d 100755 (executable)
@@ -38,11 +38,14 @@ mount --bind $t a/b \
 cat <<\EOF > exp || framework_failure_
 rm: skipping 'a/b', since it's on a different device
 EOF
-
-
-rm --one-file-system -rf a 2> out && fail=1
+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