From fdc2da7165d93c8065365999341173ad2a818833 Mon Sep 17 00:00:00 2001 From: Krzysztof Goj Date: Sun, 22 Jan 2012 01:39:59 +0100 Subject: rm: new option --dir (-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 improves compatibility with Mac OS X and BSD systems, which honor the -d option. * src/remove.c (rm_fts): Remove empty directories when requested. * src/remove.h (rm_options) [remove_empty_directories]: New member. * src/rm.c (long_opts, usage, main): Update usage and option parsing. (rm_option_init): Initialize the new member. * src/mv.c (rm_option_init): Initialize the new member. * tests/rm/d-1: New test case - successfully delete empty dir. * tests/rm/d-2: New test case - refuse to delete nonempty dir. * tests/Makefile.am (TESTS): Add them. --- NEWS | 9 +++++++++ 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, 108 insertions(+), 4 deletions(-) create mode 100755 tests/rm/d-1 create mode 100755 tests/rm/d-2 diff --git a/NEWS b/NEWS index 46d0a4131..012a63379 100644 --- a/NEWS +++ b/NEWS @@ -2,11 +2,20 @@ GNU coreutils NEWS -*- outline -*- * Noteworthy changes in release ?.? (????-??-??) [?] +** Bug fixes + df now fails when the list of mounted file systems (/etc/mtab) cannot be read, yet the file system type information is needed to process certain options like -a, -l, -t and -x. [This bug was present in "the beginning".] +** New features + + rm now accepts the --dir (-d) option which makes it remove empty directories. + Since removing empty directories is relatively safe, this option can be + used as a part of the alias rm='rm --dir'. This improves compatibility + with Mac OS X and BSD systems which also honor the -d option. + * Noteworthy changes in release 8.18 (2012-08-12) [stable] diff --git a/doc/coreutils.texi b/doc/coreutils.texi index 744b41a9f..62b31fe1a 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 ee2f5a10c..4f5708eb2 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 5ebd2ce43..61ba5f372 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 4eab2821f..f994517af 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 02809f214..a45594e42 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 273405f59..09d2658e7 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 000000000..f35e95125 --- /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 . + +. "${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 000000000..a63cff60d --- /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 . + +. "${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 -- cgit v1.2.3-54-g00ecf