From 1adbf333557a55fdf62856abd3563e9bfb457962 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

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

This change improves compatibility with Mac OS X and BSD systems
which all have the -d flag.

* 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
---
 NEWS               |  6 ++++++
 doc/coreutils.texi |  7 +++++++
 src/mv.c           |  1 +
 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       | 33 +++++++++++++++++++++++++++++++++
 9 files changed, 105 insertions(+), 4 deletions(-)
 create mode 100755 tests/rm/d-1
 create mode 100755 tests/rm/d-2

diff --git a/NEWS b/NEWS
index 385617e..ad59caa 100644
--- a/NEWS
+++ b/NEWS
@@ -2,6 +2,12 @@ GNU coreutils NEWS                                    -*- outline -*-
 
 * Noteworthy changes in release ?.? (????-??-??) [?]
 
+** New features
+
+  rm now accepts -d/--dir flag which allows it to remove empty directories.
+  As removing empty directories is relatively safe this option can be used as
+  a part of the alias rm='rm --dir'.  This change also improves compatibility
+  with Mac OS X and BSD systems which all have the -d flag.
 
 * Noteworthy changes in release 8.18 (2012-08-12) [stable]
 
diff --git a/doc/coreutils.texi b/doc/coreutils.texi
index 516ec73..a3a4960 100644
--- a/doc/coreutils.texi
+++ b/doc/coreutils.texi
@@ -8807,6 +8807,13 @@ The program accepts the following options.  Also see @ref{Common options}.
 
 @table @samp
 
+@item -d
+@itemx --dir
+@opindex -d
+@opindex --dir
+@cindex directories, removing
+Remove the listed directories if they are empty.
+
 @item -f
 @itemx --force
 @opindex -f
diff --git a/src/mv.c b/src/mv.c
index ee2f5a1..4f5708e 100644
--- a/src/mv.c
+++ b/src/mv.c
@@ -73,6 +73,7 @@ static void
 rm_option_init (struct rm_options *x)
 {
   x->ignore_missing_files = false;
+  x->remove_empty_directories = true;
   x->recursive = true;
   x->one_file_system = false;
 
diff --git a/src/remove.c b/src/remove.c
index 5ebd2ce..61ba5f3 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
+             that 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..f994517 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..a09cfe8 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -98,6 +98,8 @@ TESTS =						\
   chgrp/basic					\
   rm/dangling-symlink				\
   misc/ls-time					\
+  rm/d-1					\
+  rm/d-2					\
   rm/deep-1					\
   rm/deep-2					\
   rm/dir-no-w					\
diff --git a/tests/rm/d-1 b/tests/rm/d-1
new file mode 100755
index 0000000..f35e951
--- /dev/null
+++ b/tests/rm/d-1
@@ -0,0 +1,38 @@
+#!/bin/sh
+# Test "rm --dir --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..a63cff6
--- /dev/null
+++ b/tests/rm/d-2
@@ -0,0 +1,33 @@
+#!/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 || framework_failure_
+
+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

