Subject: bug#15926: RFE: unlink command already uses 'unlink' call; make 'rm' use 'remove' call

From: Bernhard Voelker <[email-masked]>
Date: Thu, 21 Nov 2013 17:18:35 +0100

On 11/21/2013 04:06 PM, Eric Blake wrote:
> Hard to say that it is considerable bloat without seeing a patch; we
> already know when the top-level arguments are directories thanks to 'rm -d'.

Here's a draft - not tested more than this:

   $ mkdir -p /tmp/dir /tmp/dir/sub
   $ touch /tmp/dir/file /tmp/dir/sub/other

   $ src/rm -rv --child /tmp/dir
   removed ‘/tmp/dir/file’
   removed ‘/tmp/dir/sub/other’
   removed directory: ‘/tmp/dir/sub’
   src/rm: skipping ‘/tmp/dir’, due to --children-only

   $ src/rm -rv --child /tmp/dir
   src/rm: skipping ‘/tmp/dir’, due to --children-only

   $ src/rm -rv --child /tmp/dir/.
   src/rm: skipping ‘/tmp/dir/.’, due to --children-only

Have a nice day,
Berny

 From 03d58cc281c6155d50be9b770bbac7bf73cdaf92 Mon Sep 17 00:00:00 2001
From: Bernhard Voelker <[email-masked]>
Date: Thu, 21 Nov 2013 17:11:27 +0100
Subject: [PATCH] rm: add --children-only option

FIXME
---
  src/mv.c     |  1 +
  src/remove.c | 29 ++++++++++++++++++++++++-----
  src/remove.h |  4 ++++
  src/rm.c     |  9 +++++++++
  4 files changed, 38 insertions(+), 5 deletions(-)

diff --git a/src/mv.c b/src/mv.c
index 1cfcd82..36e70a4 100644
--- a/src/mv.c
+++ b/src/mv.c
@@ -74,6 +74,7 @@ rm_option_init (struct rm_options *x)
  {
    x->ignore_missing_files = false;
    x->remove_empty_directories = true;
+  x->children_only = false;
    x->recursive = true;
    x->one_file_system = false;

diff --git a/src/remove.c b/src/remove.c
index 3d386cf..eb05cb4 100644
--- a/src/remove.c
+++ b/src/remove.c
@@ -439,8 +439,10 @@ rm_fts (FTS *fts, FTSENT *ent, struct rm_options 
const *x)
          {
            /* POSIX says:
               If the basename of a command line argument is "." or "..",
-             diagnose it and do nothing more with that argument.  */
-          if (dot_or_dotdot (last_component (ent->fts_accpath)))
+             diagnose it and do nothing more with that argument.
+             FIXME: mention --children-only.  */
+          if (! x->children_only
+              && dot_or_dotdot (last_component (ent->fts_accpath)))
              {
                error (0, 0,
                       _("refusing to remove %s or %s directory: 
skipping %s"),
@@ -468,9 +470,17 @@ rm_fts (FTS *fts, FTSENT *ent, struct rm_options 
const *x)

          if (s == RM_OK && is_empty_directory == T_YES)
            {
-            /* When we know (from prompt when in interactive mode)
-               that this is an empty directory, don't prompt twice.  */
-            s = excise (fts, ent, x, true);
+            if (FTS_ROOTLEVEL == ent->fts_level && x->children_only)
+              {
+                error (0, 0, _("skipping %s, due to --children-only"),
+                       quote (ent->fts_path));
+              }
+            else
+              {
+                /* When we know (from prompt when in interactive mode)
+                   that this is an empty directory, don't prompt twice.  */
+                s = excise (fts, ent, x, true);
+              }
              fts_skip_tree (fts, ent);
            }

@@ -492,6 +502,15 @@ rm_fts (FTS *fts, FTSENT *ent, struct rm_options 
const *x)
      case FTS_NSOK:		/* e.g., dangling symlink */
      case FTS_DEFAULT:		/* none of the above */
        {
+        if (ent->fts_info == FTS_DP
+            && x->children_only
+            && FTS_ROOTLEVEL == ent->fts_level)
+          {
+            mark_ancestor_dirs (ent);
+            error (0, 0, _("skipping %s, due to --children-only"),
+                   quote (ent->fts_path));
+            return RM_OK;
+          }
          /* With --one-file-system, do not attempt to remove a mount point.
             fts' FTS_XDEV ensures that we don't process any entries under
             the mount point.  */
diff --git a/src/remove.h b/src/remove.h
index 9ac54d4..248a470 100644
--- a/src/remove.h
+++ b/src/remove.h
@@ -52,6 +52,10 @@ struct rm_options
    /* If true, remove empty directories.  */
    bool remove_empty_directories;

+  /* If true (and the -r option is also specified), remove all children
+     of directory arguments, yet retaining the directory itself.  */
+  bool children_only;
+
    /* Pointer to the device and inode numbers of '/', when --recursive
       and preserving '/'.  Otherwise NULL.  */
    struct dev_ino *root_dev_ino;
diff --git a/src/rm.c b/src/rm.c
index 7a51eef..0634855 100644
--- a/src/rm.c
+++ b/src/rm.c
@@ -51,6 +51,7 @@ enum
    ONE_FILE_SYSTEM,
    NO_PRESERVE_ROOT,
    PRESERVE_ROOT,
+  CHILDREN_ONLY,
    PRESUME_INPUT_TTY_OPTION
  };

@@ -78,6 +79,7 @@ static struct option const long_opts[] =

    {"recursive", no_argument, NULL, 'r'},
    {"dir", no_argument, NULL, 'd'},
+  {"children-only", no_argument, NULL, CHILDREN_ONLY},
    {"verbose", no_argument, NULL, 'v'},
    {GETOPT_HELP_OPTION_DECL},
    {GETOPT_VERSION_OPTION_DECL},
@@ -156,6 +158,8 @@ Remove (unlink) the FILE(s).\n\
        --preserve-root   do not remove '/' (default)\n\
    -r, -R, --recursive   remove directories and their contents 
recursively\n\
    -d, --dir             remove empty directories\n\
+      --children-only   remove only children, yet retaining the given\n\
+                          directory arguments\n\
    -v, --verbose         explain what is being done\n\
  "), stdout);
        fputs (HELP_OPTION_DESCRIPTION, stdout);
@@ -192,6 +196,7 @@ rm_option_init (struct rm_options *x)
    x->interactive = RMI_SOMETIMES;
    x->one_file_system = false;
    x->remove_empty_directories = false;
+  x->children_only = false;
    x->recursive = false;
    x->root_dev_ino = NULL;
    x->stdin_tty = isatty (STDIN_FILENO);
@@ -296,6 +301,10 @@ main (int argc, char **argv)
            preserve_root = true;
            break;

+        case CHILDREN_ONLY:
+          x.children_only = true;
+          break;
+
          case PRESUME_INPUT_TTY_OPTION:
            x.stdin_tty = true;
            break;
-- 
1.8.4.2