I was asked off list to consider adding an option to rm
that could be enabled with an alias, and would protect
mount points specified on the command line.

I think this is useful functionality as rm will ultimately
fail to remove the mount point itself, but only after
processing the whole file system.

It's unusual to remove a whole file system as it's
generally more efficient to recreate and remount them,
and in the particular case I was told about, it was
a pretty effective DoS to the system to process
and unlink the whole tree.

Rather than add a new option, I thought it cleaner
to extend the existing --preserve-root option to support
--preserve-root=all, as that can be interpreted to
preserve the root of all specified file systems.

Attached is the proposed implementation,
to which I'll add tests and docs after comments.

An example run on my system is:

  $ rm -r --preserve-root=all /dev/shm
  rm: skipping '/dev/shm', since it's a mount point
  rm: and --preserve-root=all is in effect

cheers,
Pádraig
From 53c78143c411170cef79f75938ac0051c59cafa7 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?P=C3=A1draig=20Brady?= <p...@draigbrady.com>
Date: Sun, 10 Jun 2018 23:02:58 -0700
Subject: [PATCH] rm: add --preserve-root=all to protect mount points

* src/remove.c (rm_fts): With the --preserve-root=all extension,
skip command line arguments that are mount points.
---
 src/mv.c     |  2 ++
 src/remove.c | 35 +++++++++++++++++++++++++++++++++++
 src/remove.h |  4 ++++
 src/rm.c     | 14 +++++++++++++-
 4 files changed, 54 insertions(+), 1 deletion(-)

diff --git a/src/mv.c b/src/mv.c
index edc1e73..b6dd72d 100644
--- 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
diff --git a/src/remove.c b/src/remove.c
index eafb964..6ff837c 100644
--- a/src/remove.c
+++ b/src/remove.c
@@ -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 a mount point"),
+                             quoteaf (ent->fts_path));
+                      error (0, 0, _("and --preserve-root=all is in effect"));
+                    }
+                  fts_skip_tree (fts, ent);
+                  return RM_ERROR;
+                }
+            }
         }
 
       {
diff --git a/src/remove.h b/src/remove.h
index 2ce3a16..8fc6171 100644
--- a/src/remove.h
+++ b/src/remove.h
@@ -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 mount point.  */
+  bool preserve_all_root;
+
   /* If nonzero, stdin is a tty.  */
   bool stdin_tty;
 
diff --git a/src/rm.c b/src/rm.c
index dbc0cb7..82cf132 100644
--- 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.
@@ -192,6 +192,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 +295,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;
 
-- 
2.9.3

Reply via email to