>From 1b20708ee1245e2f7e291e226d9fbcd4c0b78d93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A1draig=20Brady?= Date: Sun, 10 Jun 2018 23:02:58 -0700 Subject: [PATCH] rm: add --preserve-root=all to protect mounts * src/remove.c (rm_fts): With the --preserve-root=all extension, skip 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 skip the cli argument if a separate file system. * tests/rm/one-file-system.sh: Add a test case. * NEWS: Mention the new feature. --- NEWS | 5 +++++ doc/coreutils.texi | 7 +++++-- src/mv.c | 2 ++ src/remove.c | 35 +++++++++++++++++++++++++++++++++++ src/remove.h | 4 ++++ src/rm.c | 19 +++++++++++++++++-- tests/rm/one-file-system.sh | 9 ++++++--- 7 files changed, 74 insertions(+), 7 deletions(-) diff --git a/NEWS b/NEWS index 101afc0..23526d1 100644 --- a/NEWS +++ b/NEWS @@ -46,6 +46,11 @@ GNU coreutils NEWS -*- outline -*- 'cp --force file symlink' now removes the symlink even if it is self referential. +** New Features + + rm --preserve-root now supports the --preserve-root=all option to + skip 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. diff --git a/doc/coreutils.texi b/doc/coreutils.texi index c28b8d0..2073f07 100644 --- a/doc/coreutils.texi +++ b/doc/coreutils.texi @@ -9812,7 +9812,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 @@ -9825,14 +9824,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, any command line argument that +is on a separate file system to its parent is also skipped. @item --no-preserve-root @opindex --no-preserve-root 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..cb43ca3 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 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; + } + } } { diff --git a/src/remove.h b/src/remove.h index 2ce3a16..55d1bdd 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 separate device. */ + 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..8a44cd5 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. @@ -151,7 +151,10 @@ 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); with 'all', skip\n\ + any command line argument on a separate device\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 +195,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 +298,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; diff --git a/tests/rm/one-file-system.sh b/tests/rm/one-file-system.sh index 1098331..d0dbac5 100755 --- a/tests/rm/one-file-system.sh +++ b/tests/rm/one-file-system.sh @@ -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 -- 2.9.3