bug-coreutils
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: rm issue


From: Eric Blake
Subject: Re: rm issue
Date: Sat, 18 Feb 2006 10:58:04 -0700
User-agent: Mozilla Thunderbird 1.0.2 (Windows/20050317)

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

According to Jim Meyering on 2/17/2006 9:14 AM:
> Eric Blake <address@hidden> wrote:
> 
>>So you propose that --interactive alone be equivalant to
>>--interactive=always or -i, and that the long option for -I is
>>--interactive=once.  Is it worth having a shorter spelling for the long
>>option for -I?
> 
> 
> No.  Those who want `short' can use -I.
> Although --ask-once does sound nice, I want to keep things minimal,
> in case we end up providing similar functionality for other commands,
> where adding a long-named synonym might make an existing abbreviation
> ambiguous -- --ask-once would do just that to cp --a(rchive).

With that, here goes.  Changes from the first submittal:  --interactive
without WHEN defaults to -i, NEWS and coreutils.texi touched up to match
this change, and 2 test files are added.

ChangeLog:
2006-02-18  Eric Blake  <address@hidden>

        * TODO (rm): Implement rm -I.
        * NEWS: Document it, along with change to rm --interactive.
        * src/rm.c (INTERACTIVE_OPTION): New enum value.
        (interactive_type): New enum.
        (long_opts): Let interactive take an optional argument.
        (interactive_args, interactive_types): New option arguments.
        (usage): Document -I, --interactive=WHEN.  Use program_name
        instead of a basename.
        (main): New -I option, new behavior to --interactive.
        * tests/rm/interactive-once: New tests.
        * tests/rm/interactive-always: Ditto.
        * tests/rm/Makefile.am (TESTS): Run them.

doc/ChangeLog:
2006-02-18  Eric Blake  <address@hidden>

        * coreutils.texi (rm invocation): Document new -I option, and new
        --interactive behavior.

- --
Life is short - so eat dessert first!

Eric Blake             address@hidden
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.1 (Cygwin)
Comment: Public key at home.comcast.net/~ericblake/eblake.gpg
Comment: Using GnuPG with Thunderbird - http://enigmail.mozdev.org

iD8DBQFD92Ar84KuGfSFAYARAqMqAKCCnnycir0UCfAtE3IGy/JIz171tgCfeCg9
fIWbng1br9auGCIwYJrbYSE=
=rWII
-----END PGP SIGNATURE-----
Index: TODO
===================================================================
RCS file: /sources/coreutils/coreutils/TODO,v
retrieving revision 1.101
diff -u -p -r1.101 TODO
--- TODO        16 Oct 2005 10:36:02 -0000      1.101
+++ TODO        18 Feb 2006 17:46:50 -0000
@@ -215,9 +215,3 @@ Adapt tools like wc, tr, fmt, etc. (most
   (preferably `no') cost when operating in single-byte mode.
 
 Remove all uses of the `register' keyword
-
-rm: add support for a -I option, like that from FreeBSD's rm:
-   -I    Request confirmation once if more than three files are being
-         removed or if a directory is being recursively removed.  This
-         is a far less intrusive option than -i yet provides almost
-         the same level of protection against mistakes.
Index: NEWS
===================================================================
RCS file: /sources/coreutils/coreutils/NEWS,v
retrieving revision 1.359
diff -u -p -r1.359 NEWS
--- NEWS        11 Feb 2006 19:25:26 -0000      1.359
+++ NEWS        18 Feb 2006 17:46:51 -0000
@@ -26,6 +26,9 @@ GNU coreutils NEWS                      
   if your locale settings appear to be messed up.  This change
   attempts to have the default be the best of both worlds.
 
+  rm --interactive now takes an optional argument, although the
+  default of using no argument still acts like -i.
+
   mkfifo and mknod no longer set special mode bits (setuid, setgid,
   and sticky) with the -m option.
 
@@ -71,6 +74,12 @@ GNU coreutils NEWS                      
   updating its access time, on hosts that support this (currently only
   Linux kernels, version 2.6.8 and later).
 
+  rm now accepts the -I (--interactive=once) option.  This new option
+  prompts once if rm is invoked recursively or if more than three
+  files are being deleted, which is less intrusive than -i prompting
+  for every file, but provides almost the same level of protection
+  against mistakes.
+
   sort now accepts the --random-sort (-R) option and `R' ordering option,
   as well as the --seed=STRING option.
 
Index: src/rm.c
===================================================================
RCS file: /sources/coreutils/coreutils/src/rm.c,v
retrieving revision 1.138
diff -u -p -r1.138 rm.c
--- src/rm.c    6 Feb 2006 08:00:39 -0000       1.138
+++ src/rm.c    18 Feb 2006 17:46:52 -0000
@@ -49,6 +49,7 @@
 #include <assert.h>
 
 #include "system.h"
+#include "argmatch.h"
 #include "dirname.h"
 #include "error.h"
 #include "lstat.h"
@@ -56,6 +57,7 @@
 #include "quotearg.h"
 #include "remove.h"
 #include "root-dev-ino.h"
+#include "yesno.h"
 
 /* The official name of this program (e.g., no `g' prefix).  */
 #define PROGRAM_NAME "rm"
@@ -70,16 +72,24 @@ char *program_name;
    non-character as a pseudo short option, starting with CHAR_MAX + 1.  */
 enum
 {
-  NO_PRESERVE_ROOT = CHAR_MAX + 1,
+  INTERACTIVE_OPTION = CHAR_MAX + 1,
+  NO_PRESERVE_ROOT,
   PRESERVE_ROOT,
   PRESUME_INPUT_TTY_OPTION
 };
 
+enum interactive_type
+  {
+    interactive_never,         /* 0: no option or --interactive=never */
+    interactive_once,          /* 1: -I or --interactive=once */
+    interactive_always         /* 2: default, -i or --interactive=always */
+  };
+
 static struct option const long_opts[] =
 {
   {"directory", no_argument, NULL, 'd'},
   {"force", no_argument, NULL, 'f'},
-  {"interactive", no_argument, NULL, 'i'},
+  {"interactive", optional_argument, NULL, INTERACTIVE_OPTION},
 
   {"no-preserve-root", no_argument, NULL, NO_PRESERVE_ROOT},
   {"preserve-root", no_argument, NULL, PRESERVE_ROOT},
@@ -97,6 +107,20 @@ static struct option const long_opts[] =
   {NULL, 0, NULL, 0}
 };
 
+static char const *const interactive_args[] =
+{
+  "never", "no", "none",
+  "once",
+  "always", "yes", NULL
+};
+static enum interactive_type const interactive_types[] =
+{
+  interactive_never, interactive_never, interactive_never,
+  interactive_once,
+  interactive_always, interactive_always
+};
+ARGMATCH_VERIFY (interactive_args, interactive_types);
+
 /* Advise the user about invalid usages like "rm -foo" if the file
    "-foo" exists, assuming ARGC and ARGV are as with `main'.  */
 
@@ -132,13 +156,19 @@ usage (int status)
             program_name);
   else
     {
-      char *base = base_name (program_name);
       printf (_("Usage: %s [OPTION]... FILE...\n"), program_name);
       fputs (_("\
 Remove (unlink) the FILE(s).\n\
 \n\
   -f, --force           ignore nonexistent files, never prompt\n\
-  -i, --interactive     prompt before any removal\n\
+  -i                    prompt before every removal\n\
+"), stdout);
+      fputs (_("\
+  -I                    prompt once before removing more than three files, 
or\n\
+                          when removing recursively.  Less intrusive than 
-i,\n\
+                          while still giving protection against most 
mistakes\n\
+      --interactive[=WHEN]  prompt according to WHEN: never, once (-I), or\n\
+                          always (-i).  Without WHEN, prompt always\n\
 "), stdout);
       fputs (_("\
       --no-preserve-root  do not treat `/' specially (the default)\n\
@@ -161,7 +191,7 @@ use one of these commands:\n\
 \n\
   %s ./-foo\n\
 "),
-             base, base);
+             program_name, program_name);
       fputs (_("\
 \n\
 Note that if you use rm to remove a file, it is usually possible to recover\n\
@@ -193,6 +223,7 @@ main (int argc, char **argv)
 {
   bool preserve_root = false;
   struct rm_options x;
+  bool prompt_once = false;
   int c;
 
   initialize_main (&argc, &argv);
@@ -205,7 +236,7 @@ main (int argc, char **argv)
 
   rm_option_init (&x);
 
-  while ((c = getopt_long (argc, argv, "dfirvR", long_opts, NULL)) != -1)
+  while ((c = getopt_long (argc, argv, "dfirvIR", long_opts, NULL)) != -1)
     {
       switch (c)
        {
@@ -219,11 +250,19 @@ main (int argc, char **argv)
        case 'f':
          x.interactive = false;
          x.ignore_missing_files = true;
+         prompt_once = false;
          break;
 
        case 'i':
          x.interactive = true;
          x.ignore_missing_files = false;
+         prompt_once = false;
+         break;
+
+       case 'I':
+         x.interactive = false;
+         x.ignore_missing_files = false;
+         prompt_once = true;
          break;
 
        case 'r':
@@ -231,6 +270,36 @@ main (int argc, char **argv)
          x.recursive = true;
          break;
 
+       case INTERACTIVE_OPTION:
+         {
+           int i;
+           if (optarg)
+             i = XARGMATCH ("--interactive", optarg, interactive_args,
+                            interactive_types);
+           else
+             i = interactive_always;
+           switch (i)
+             {
+             case interactive_never:
+               x.interactive = false;
+               prompt_once = false;
+               break;
+
+             case interactive_once:
+               x.interactive = false;
+               x.ignore_missing_files = false;
+               prompt_once = true;
+               break;
+
+             case interactive_always:
+               x.interactive = true;
+               x.ignore_missing_files = false;
+               prompt_once = false;
+               break;
+             }
+           break;
+         }
+
        case NO_PRESERVE_ROOT:
          preserve_root = false;
          break;
@@ -279,6 +348,16 @@ main (int argc, char **argv)
     size_t n_files = argc - optind;
     char const *const *file = (char const *const *) argv + optind;
 
+    if (prompt_once && (x.recursive || 3 < n_files))
+      {
+       fprintf (stderr,
+                (x.recursive
+                 ? _("%s: remove all arguments recursively? ")
+                 : _("%s: remove all arguments? ")),
+                program_name);
+       if (!yesno ())
+         exit (EXIT_SUCCESS);
+      }
     enum RM_status status = rm (n_files, file, &x);
     assert (VALID_STATUS (status));
     exit (status == RM_ERROR ? EXIT_FAILURE : EXIT_SUCCESS);
Index: tests/rm/Makefile.am
===================================================================
RCS file: /sources/coreutils/coreutils/tests/rm/Makefile.am,v
retrieving revision 1.32
diff -u -p -r1.32 Makefile.am
--- tests/rm/Makefile.am        11 Feb 2006 18:03:52 -0000      1.32
+++ tests/rm/Makefile.am        18 Feb 2006 17:46:52 -0000
@@ -13,6 +13,7 @@ TESTS = \
   cycle i-no-r fail-eperm \
   dangling-symlink rm1 rm2 rm3 rm4 rm5 \
   unread2 r-1 r-2 r-3 i-1 ir-1 f-1 sunos-1 deep-1 hash \
+  interactive-always interactive-once \
   isatty # unreadable empty-name
 EXTRA_DIST = $(TESTS)
 TESTS_ENVIRONMENT = \
Index: tests/rm/interactive-once
===================================================================
RCS file: tests/rm/interactive-once
diff -N tests/rm/interactive-once
--- /dev/null   1 Jan 1970 00:00:00 -0000
+++ tests/rm/interactive-once   18 Feb 2006 17:46:52 -0000
@@ -0,0 +1,116 @@
+#!/bin/sh
+# Test the -I option added to coreutils 6.0
+
+test=interactive-once
+
+if test "$VERBOSE" = yes; then
+  set -x
+  rm --version
+fi
+
+. $srcdir/../lang-default
+
+pwd=`pwd`
+t0=`echo "$0"|sed 's,.*/,,'`.tmp; tmp=$t0/$$
+trap 'status=$?; cd $pwd; chmod -R u+rwx $t0; rm -rf $t0 && exit $status' 0
+trap '(exit $?); exit $?' 1 2 13 15
+
+framework_failure=0
+mkdir -p $tmp || framework_failure=1
+cd $tmp || framework_failure=1
+
+mkdir -p dir1-1 dir2-1 dir2-2 || framework_failure=1
+touch file1-1 file2-1 file2-2 file2-3 file3-1 file3-2 file3-3 file3-4 \
+  || framework_failure=1
+echo y > $test.Iy || framework_failure=1
+echo n > $test.In || framework_failure=1
+rm -f out err || framework_failure=1
+
+if test $framework_failure = 1; then
+  echo "$0: failure in testing framework" 1>&2
+  (exit 1); exit 1
+fi
+
+fail=0
+
+# The prompt has a trailing space, and no newline, so an extra
+# 'echo .' is inserted after each rm to make it obvious what was asked.
+
+echo 'one file, no recursion' > err || fail=1
+rm -I file1-* < $test.In >> out 2>> err || fail=1
+echo . >> err || fail=1
+test -f file1-1 && fail=1
+
+echo 'three files, no recursion' >> err || fail=1
+rm -I file2-* < $test.In >> out 2>> err || fail=1
+echo . >> err || fail=1
+test -f file2-1 && fail=1
+test -f file2-2 && fail=1
+test -f file2-3 && fail=1
+
+echo 'four files, no recursion, answer no' >> err || fail=1
+rm -I file3-* < $test.In >> out 2>> err || fail=1
+echo . >> err || fail=1
+test -f file3-1 || fail=1
+test -f file3-2 || fail=1
+test -f file3-3 || fail=1
+test -f file3-4 || fail=1
+
+echo 'four files, no recursion, answer yes' >> err || fail=1
+rm -I file3-* < $test.Iy >> out 2>> err || fail=1
+echo . >> err || fail=1
+test -f file3-1 && fail=1
+test -f file3-2 && fail=1
+test -f file3-3 && fail=1
+test -f file3-4 && fail=1
+
+echo 'one file, recursion, answer no' >> err || fail=1
+rm -I -R dir1-* < $test.In >> out 2>> err || fail=1
+echo . >> err || fail=1
+test -d dir1-1 || fail=1
+
+echo 'one file, recursion, answer yes' >> err || fail=1
+rm -I -R dir1-* < $test.Iy >> out 2>> err || fail=1
+echo . >> err || fail=1
+test -d dir1-1 && fail=1
+
+echo 'multiple files, recursion, answer no' >> err || fail=1
+rm -I -R dir2-* < $test.In >> out 2>> err || fail=1
+echo . >> err || fail=1
+test -d dir2-1 || fail=1
+test -d dir2-2 || fail=1
+
+echo 'multiple files, recursion, answer yes' >> err || fail=1
+rm -I -R dir2-* < $test.Iy >> out 2>> err || fail=1
+echo . >> err || fail=1
+test -d dir2-1 && fail=1
+test -d dir2-2 && fail=1
+
+cat <<\EOF > expout || fail=1
+EOF
+cat <<\EOF > experr || fail=1
+one file, no recursion
+.
+three files, no recursion
+.
+four files, no recursion, answer no
+rm: remove all arguments? .
+four files, no recursion, answer yes
+rm: remove all arguments? .
+one file, recursion, answer no
+rm: remove all arguments recursively? .
+one file, recursion, answer yes
+rm: remove all arguments recursively? .
+multiple files, recursion, answer no
+rm: remove all arguments recursively? .
+multiple files, recursion, answer yes
+rm: remove all arguments recursively? .
+EOF
+
+cmp out expout || fail=1
+cmp err experr || fail=1
+test $fail = 1 && {
+  diff out expout 2> /dev/null; diff err experr 2> /dev/null
+}
+
+(exit $fail); exit $fail
Index: tests/rm/interactive-always
===================================================================
RCS file: tests/rm/interactive-always
diff -N tests/rm/interactive-always
--- /dev/null   1 Jan 1970 00:00:00 -0000
+++ tests/rm/interactive-always 18 Feb 2006 17:46:52 -0000
@@ -0,0 +1,96 @@
+#!/bin/sh
+# Test the --interactive[=WHEN] changes added to coreutils 6.0
+
+test=interactive-always
+
+if test "$VERBOSE" = yes; then
+  set -x
+  rm --version
+fi
+
+. $srcdir/../lang-default
+
+pwd=`pwd`
+t0=`echo "$0"|sed 's,.*/,,'`.tmp; tmp=$t0/$$
+trap 'status=$?; cd $pwd; chmod -R u+rwx $t0; rm -rf $t0 && exit $status' 0
+trap '(exit $?); exit $?' 1 2 13 15
+
+framework_failure=0
+mkdir -p $tmp || framework_failure=1
+cd $tmp || framework_failure=1
+
+touch file1-1 file1-2 file2-1 file2-2 file3-1 file3-2 file4-1 file4-2 \
+  || framework_failure=1
+# If asked, answer no to first question, then yes to second.
+echo 'n
+y' > $test.I || framework_failure=1
+rm -f out err || framework_failure=1
+
+if test $framework_failure = 1; then
+  echo "$0: failure in testing framework" 1>&2
+  (exit 1); exit 1
+fi
+
+fail=0
+
+# The prompt has a trailing space, and no newline, so an extra
+# 'echo .' is inserted after each rm to make it obvious what was asked.
+
+echo 'no WHEN' > err || fail=1
+rm -R --interactive file1-* < $test.I >> out 2>> err || fail=1
+echo . >> err || fail=1
+test -f file1-1 || fail=1
+test -f file1-2 && fail=1
+
+echo 'WHEN=never' >> err || fail=1
+rm -R --interactive=never file2-* < $test.I >> out 2>> err || fail=1
+echo . >> err || fail=1
+test -f file2-1 && fail=1
+test -f file2-2 && fail=1
+
+echo 'WHEN=once' >> err || fail=1
+rm -R --interactive=once file3-* < $test.I >> out 2>> err || fail=1
+echo . >> err || fail=1
+test -f file3-1 || fail=1
+test -f file3-2 || fail=1
+
+echo 'WHEN=always' >> err || fail=1
+rm -R --interactive=always file4-* < $test.I >> out 2>> err || fail=1
+echo . >> err || fail=1
+test -f file4-1 || fail=1
+test -f file4-2 && fail=1
+
+echo '-f overrides --interactive' >> err || fail=1
+rm -R --interactive=once -f file1-* < $test.I >> out 2>> err || fail=1
+echo . >> err || fail=1
+test -f file1-1 && fail=1
+
+echo '--interactive overrides -f' >> err || fail=1
+rm -R -f --interactive=once file4-* < $test.I >> out 2>> err || fail=1
+echo . >> err || fail=1
+test -f file4-1 || fail=1
+
+cat <<\EOF > expout || fail=1
+EOF
+cat <<\EOF > experr || fail=1
+no WHEN
+rm: remove regular empty file `file1-1'? rm: remove regular empty file 
`file1-2'? .
+WHEN=never
+.
+WHEN=once
+rm: remove all arguments recursively? .
+WHEN=always
+rm: remove regular empty file `file4-1'? rm: remove regular empty file 
`file4-2'? .
+-f overrides --interactive
+.
+--interactive overrides -f
+rm: remove all arguments recursively? .
+EOF
+
+cmp out expout || fail=1
+cmp err experr || fail=1
+test $fail = 1 && {
+  diff out expout 2> /dev/null; diff err experr 2> /dev/null
+}
+
+(exit $fail); exit $fail
Index: doc/coreutils.texi
===================================================================
RCS file: /sources/coreutils/coreutils/doc/coreutils.texi,v
retrieving revision 1.311
diff -u -p -r1.311 coreutils.texi
--- doc/coreutils.texi  12 Feb 2006 08:48:42 -0000      1.311
+++ doc/coreutils.texi  18 Feb 2006 17:46:54 -0000
@@ -7327,10 +7327,16 @@ rm address@hidden@dots{} address@hidden@do
 @end example
 
 @cindex prompting, and @command{rm}
-If a file is unwritable, standard input is a terminal, and the @option{-f}
-or @option{--force} option is not given, or the @option{-i} or
address@hidden option @emph{is} given, @command{rm} prompts the user
-for whether to remove the file.
+If the @option{-I} or @option{--interactive=once} option is given,
+and there are more than three files or the @option{-r}, @option{-R},
+or @option{--recursive} are given, then @command{rm} prompts the user
+for whether to proceed with the entire operation.  If the response is
+not affirmitive, the entire command is aborted.
+
+Otherwise, if a file is unwritable, standard input is a terminal, and
+the @option{-f} or @option{--force} option is not given, or the
address@hidden or @option{--interactive=always} option @emph{is} given,
address@hidden prompts the user for whether to remove the file.
 If the response is not affirmative, the file is skipped.
 
 @emph{Warning}: If you use @command{rm} to remove a file, it is usually
@@ -7349,12 +7355,37 @@ Ignore nonexistent files and never promp
 Ignore any previous @option{--interactive} (@option{-i}) option.
 
 @item -i
address@hidden --interactive
 @opindex -i
address@hidden --interactive
 Prompt whether to remove each file.
 If the response is not affirmative, the file is skipped.
 Ignore any previous @option{--force} (@option{-f}) option.
+Equivalent to @option{--interactive=always}.
+
address@hidden -I
address@hidden -I
+Prompt once whether to proceed with the command, if more than three
+files are named or if a recursive removal is requested.  Ignore any
+previous @option{--force} (@option{-f}) option.  Equivalent to
address@hidden
+
address@hidden --interactive address@hidden
address@hidden --interactive
+Specify when to issue an interactive prompt.  @var{when} may be
+omitted, or one of:
address@hidden @bullet
address@hidden never
address@hidden never @r{interactive option}
+- Do not prompt at all.
address@hidden once
address@hidden once @r{interactive option}
+- Prompt once if more than three files are named or if a recursive
+removal is requested.  Equivalent to @option{-I}.
address@hidden always
address@hidden always @r{interactive option}
+- Prompt for every file being removed.  Equivalent to @option{-i}.
address@hidden itemize
+Specifying @option{--interactive} and no @var{when} is equivalent to
address@hidden
 
 @itemx --preserve-root
 @opindex --preserve-root

reply via email to

[Prev in Thread] Current Thread [Next in Thread]