From e215d0bf856ac161d1df3038890b434565fc22c2 Mon Sep 17 00:00:00 2001
From: Krzysztof Goj <krzysztof.goj@gmail.com>
Date: Sun, 22 Jan 2012 01:39:59 +0100
Subject: [PATCH] rm: new option (-d) to remove empty directories

Adds new option to rm (-d/--dir), which allows removal of
empty directories, while still safely disallowing removal
of non-empty ones.

* src/remove.c (rm_fts): allow removal of empty dir if the option is set
* src/remove.h (rm_options): new option - remove_empty_directories
* src/rm.c (long_opts, usage, main): usage && option parsing
* tests/Makefile.am: added new test cases (d-1, d-2)
* tests/rm/d-1: new test case - successfully delete empty dir
* tests/rm/d-2: new test case - refuse to delete nonempty dir
---
 src/remove.c      | 10 +++++++---
 src/remove.h      |  3 +++
 src/rm.c          |  9 ++++++++-
 tests/Makefile.am |  2 ++
 tests/rm/d-1      | 38 ++++++++++++++++++++++++++++++++++++++
 tests/rm/d-2      | 34 ++++++++++++++++++++++++++++++++++
 6 files changed, 92 insertions(+), 4 deletions(-)
 create mode 100755 tests/rm/d-1
 create mode 100755 tests/rm/d-2

diff --git a/src/remove.c b/src/remove.c
index 5ebd2ce..58a9b38 100644
--- a/src/remove.c
+++ b/src/remove.c
@@ -414,11 +414,15 @@ rm_fts (FTS *fts, FTSENT *ent, struct rm_options const *x)
   switch (ent->fts_info)
     {
     case FTS_D:			/* preorder directory */
-      if (! x->recursive)
+      if (! x->recursive
+          && !(x->remove_empty_directories
+               && is_empty_dir (fts->fts_cwd_fd, ent->fts_accpath)))
         {
-          /* This is the first (pre-order) encounter with a directory.
+          /* This is the first (pre-order) encounter with a directory
+           * which we can not delete.
              Not recursive, so arrange to skip contents.  */
-          error (0, EISDIR, _("cannot remove %s"), quote (ent->fts_path));
+          int err = x->remove_empty_directories ? ENOTEMPTY : EISDIR;
+          error (0, err, _("cannot remove %s"), quote (ent->fts_path));
           mark_ancestor_dirs (ent);
           fts_skip_tree (fts, ent);
           return RM_ERROR;
diff --git a/src/remove.h b/src/remove.h
index 4eab282..cd3cb29 100644
--- a/src/remove.h
+++ b/src/remove.h
@@ -49,6 +49,9 @@ struct rm_options
   /* If true, recursively remove directories.  */
   bool recursive;
 
+  /* If true, remove empty directories. */
+  bool remove_empty_directories;
+
   /* 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 02809f2..a45594e 100644
--- a/src/rm.c
+++ b/src/rm.c
@@ -77,6 +77,7 @@ static struct option const long_opts[] =
   {"-presume-input-tty", no_argument, NULL, PRESUME_INPUT_TTY_OPTION},
 
   {"recursive", no_argument, NULL, 'r'},
+  {"dir", no_argument, NULL, 'd'},
   {"verbose", no_argument, NULL, 'v'},
   {GETOPT_HELP_OPTION_DECL},
   {GETOPT_VERSION_OPTION_DECL},
@@ -154,6 +155,7 @@ Remove (unlink) the FILE(s).\n\
       --no-preserve-root  do not treat '/' specially\n\
       --preserve-root   do not remove '/' (default)\n\
   -r, -R, --recursive   remove directories and their contents recursively\n\
+  -d, --dir             remove empty directories\n\
   -v, --verbose         explain what is being done\n\
 "), stdout);
       fputs (HELP_OPTION_DESCRIPTION, stdout);
@@ -189,6 +191,7 @@ rm_option_init (struct rm_options *x)
   x->ignore_missing_files = false;
   x->interactive = RMI_SOMETIMES;
   x->one_file_system = false;
+  x->remove_empty_directories = false;
   x->recursive = false;
   x->root_dev_ino = NULL;
   x->stdin_tty = isatty (STDIN_FILENO);
@@ -220,10 +223,14 @@ main (int argc, char **argv)
   /* Try to disable the ability to unlink a directory.  */
   priv_set_remove_linkdir ();
 
-  while ((c = getopt_long (argc, argv, "firvIR", long_opts, NULL)) != -1)
+  while ((c = getopt_long (argc, argv, "dfirvIR", long_opts, NULL)) != -1)
     {
       switch (c)
         {
+        case 'd':
+          x.remove_empty_directories = true;
+          break;
+
         case 'f':
           x.interactive = RMI_NEVER;
           x.ignore_missing_files = true;
diff --git a/tests/Makefile.am b/tests/Makefile.am
index edc04b4..8caffda 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -125,6 +125,8 @@ TESTS =						\
   rm/r-2					\
   rm/r-3					\
   rm/r-4					\
+  rm/d-1					\
+  rm/d-2					\
   rm/readdir-bug				\
   rm/rm1					\
   touch/empty-file				\
diff --git a/tests/rm/d-1 b/tests/rm/d-1
new file mode 100755
index 0000000..c4a8ab5
--- /dev/null
+++ b/tests/rm/d-1
@@ -0,0 +1,38 @@
+#!/bin/sh
+# Test "rm -d --verbose".
+
+# Copyright (C) 2012 Free Software Foundation, Inc.
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+. "${srcdir=.}/init.sh"; path_prepend_ ../src
+print_ver_ rm
+
+mkdir a || framework_failure_
+> b || framework_failure_
+
+rm --verbose --dir a b > out || fail=1
+
+cat <<\EOF > exp || framework_failure_
+removed directory: 'a'
+removed 'b'
+EOF
+
+test -e a && fail=1
+test -e b && fail=1
+
+# Compare expected and actual output.
+compare exp out || fail=1
+
+Exit $fail
diff --git a/tests/rm/d-2 b/tests/rm/d-2
new file mode 100755
index 0000000..932dace
--- /dev/null
+++ b/tests/rm/d-2
@@ -0,0 +1,34 @@
+#!/bin/sh
+# Ensure that 'rm -d dir' (i.e., without --recursive) gives a reasonable
+# diagnostic when failing.
+
+# Copyright (C) 2012 Free Software Foundation, Inc.
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+. "${srcdir=.}/init.sh"; path_prepend_ ../src
+print_ver_ rm
+
+mkdir d || framework_failure_
+> d/a
+
+
+rm -d d 2> out && fail=1
+printf "%s\n" \
+    "rm: cannot remove 'd': Directory not empty" \
+    > exp || framework_failure_
+
+compare exp out || fail=1
+
+Exit $fail
-- 
1.7.11.4

