From fa48dbf2f11c9ba7dd55e22a420c6b08d54aa706 Mon Sep 17 00:00:00 2001 From: Krzysztof Goj 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 | 18 ++++++++++++------ src/remove.h | 3 +++ src/rm.c | 9 ++++++++- tests/Makefile.am | 2 ++ tests/rm/d-1 | 42 ++++++++++++++++++++++++++++++++++++++++++ tests/rm/d-2 | 34 ++++++++++++++++++++++++++++++++++ 6 files changed, 101 insertions(+), 7 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 60d9110..acd4164 100644 --- a/src/remove.c +++ b/src/remove.c @@ -460,12 +460,18 @@ rm_fts (FTS *fts, FTSENT *ent, struct rm_options const *x) case FTS_D: /* preorder directory */ if (! x->recursive) { - /* This is the first (pre-order) encounter with a directory. - Not recursive, so arrange to skip contents. */ - error (0, EISDIR, _("cannot remove %s"), quote (ent->fts_path)); - mark_ancestor_dirs (ent); - fts_skip_tree (fts, ent); - return RM_ERROR; + if (! x->remove_empty_directories + || !is_empty_dir (fts->fts_cwd_fd, ent->fts_accpath)) + { + /* This is the first (pre-order) encounter with a directory + * which we can not delete. + Not recursive, so arrange to skip contents. */ + 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; + } } /* Perform checks that can apply only for command-line arguments. */ diff --git a/src/remove.h b/src/remove.h index c4c59a2..53c6c12 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 6eeeafc..e09d884 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 8b670fc..dcbe2c8 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -122,6 +122,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..69c5860 --- /dev/null +++ b/tests/rm/d-1 @@ -0,0 +1,42 @@ +#!/bin/sh +# Test "rm -r --verbose". + +# Copyright (C) 1997-1998, 2000, 2002, 2004, 2006-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 . + +test=d-1 + +. "${srcdir=.}/init.sh"; path_prepend_ ../src +print_ver_ rm + +mkdir a || framework_failure_ +> b || framework_failure_ + +cat <<\EOF > $test.E || framework_failure_ +removed directory: 'a' +removed 'b' +EOF + +rm --verbose -d a b > $test.O || fail=1 + +if test -e a -o -e b; then +fail=1 +fi + +# Compare expected and actual output. +compare $test.E $test.O || fail=1 + +Exit $fail diff --git a/tests/rm/d-2 b/tests/rm/d-2 new file mode 100755 index 0000000..97e9250 --- /dev/null +++ b/tests/rm/d-2 @@ -0,0 +1,34 @@ +#!/bin/sh +# Ensure that 'rm dir' (i.e., without --recursive) gives a reasonable +# diagnostic when failing. + +# Copyright (C) 2005-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 . + +. "${srcdir=.}/init.sh"; path_prepend_ ../src +print_ver_ rm + +mkdir d || framework_failure_ +> d/a + + +rm -d d 2> out && fail=1 +cat <<\EOF > exp || fail=1 +rm: cannot remove 'd': Directory not empty +EOF + +compare exp out || fail=1 + +Exit $fail -- 1.7.8.3