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

From: Jim Meyering <[email-masked]>
Date: Thu, 21 Nov 2013 09:02:07 -0800

On Thu, Nov 21, 2013 at 8:18 AM, Bernhard Voelker
<[email-masked]> wrote:
> 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\
...

Thanks for the quick work.

What do you think about saying something simple yet imprecise in --help, like
"when recursive, suppress removal of any directory named on the command line"
with the caveat that the texinfo doc would add precision: but a
command-line-specified directory may still end up being removed when
it is also a subdirectory of another command line argument.