1/* `rm' file deletion utility for GNU.
2   Copyright (C) 1988, 1990-1991, 1994-2010 Free Software Foundation, Inc.
3
4   This program is free software: you can redistribute it and/or modify
5   it under the terms of the GNU General Public License as published by
6   the Free Software Foundation, either version 3 of the License, or
7   (at your option) any later version.
8
9   This program is distributed in the hope that it will be useful,
10   but WITHOUT ANY WARRANTY; without even the implied warranty of
11   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12   GNU General Public License for more details.
13
14   You should have received a copy of the GNU General Public License
15   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
16
17/* Initially written by Paul Rubin, David MacKenzie, and Richard Stallman.
18   Reworked to use chdir and avoid recursion, and later, rewritten
19   once again, to use fts, by Jim Meyering.  */
20
21#include <config.h>
22#include <stdio.h>
23#include <getopt.h>
24#include <sys/types.h>
25#include <assert.h>
26
27#include "system.h"
28#include "argmatch.h"
29#include "error.h"
30#include "quote.h"
31#include "quotearg.h"
32#include "remove.h"
33#include "root-dev-ino.h"
34#include "yesno.h"
35#include "priv-set.h"
36
37/* The official name of this program (e.g., no `g' prefix).  */
38#define PROGRAM_NAME "rm"
39
40#define AUTHORS \
41  proper_name ("Paul Rubin"), \
42  proper_name ("David MacKenzie"), \
43  proper_name ("Richard M. Stallman"), \
44  proper_name ("Jim Meyering")
45
46/* For long options that have no equivalent short option, use a
47   non-character as a pseudo short option, starting with CHAR_MAX + 1.  */
48enum
49{
50  INTERACTIVE_OPTION = CHAR_MAX + 1,
51  ONE_FILE_SYSTEM,
52  NO_PRESERVE_ROOT,
53  PRESERVE_ROOT,
54  PRESUME_INPUT_TTY_OPTION
55};
56
57enum interactive_type
58  {
59    interactive_never,		/* 0: no option or --interactive=never */
60    interactive_once,		/* 1: -I or --interactive=once */
61    interactive_always		/* 2: default, -i or --interactive=always */
62  };
63
64static struct option const long_opts[] =
65{
66  {"directory", no_argument, NULL, 'd'},
67  {"force", no_argument, NULL, 'f'},
68  {"interactive", optional_argument, NULL, INTERACTIVE_OPTION},
69
70  {"one-file-system", no_argument, NULL, ONE_FILE_SYSTEM},
71  {"no-preserve-root", no_argument, NULL, NO_PRESERVE_ROOT},
72  {"preserve-root", no_argument, NULL, PRESERVE_ROOT},
73
74  /* This is solely for testing.  Do not document.  */
75  /* It is relatively difficult to ensure that there is a tty on stdin.
76     Since rm acts differently depending on that, without this option,
77     it'd be harder to test the parts of rm that depend on that setting.  */
78  {"-presume-input-tty", no_argument, NULL, PRESUME_INPUT_TTY_OPTION},
79
80  {"recursive", no_argument, NULL, 'r'},
81  {"verbose", no_argument, NULL, 'v'},
82  {GETOPT_HELP_OPTION_DECL},
83  {GETOPT_VERSION_OPTION_DECL},
84  {NULL, 0, NULL, 0}
85};
86
87static char const *const interactive_args[] =
88{
89  "never", "no", "none",
90  "once",
91  "always", "yes", NULL
92};
93static enum interactive_type const interactive_types[] =
94{
95  interactive_never, interactive_never, interactive_never,
96  interactive_once,
97  interactive_always, interactive_always
98};
99ARGMATCH_VERIFY (interactive_args, interactive_types);
100
101/* Advise the user about invalid usages like "rm -foo" if the file
102   "-foo" exists, assuming ARGC and ARGV are as with `main'.  */
103
104static void
105diagnose_leading_hyphen (int argc, char **argv)
106{
107  /* OPTIND is unreliable, so iterate through the arguments looking
108     for a file name that looks like an option.  */
109  int i;
110
111  for (i = 1; i < argc; i++)
112    {
113      char const *arg = argv[i];
114      struct stat st;
115
116      if (arg[0] == '-' && arg[1] && lstat (arg, &st) == 0)
117        {
118          fprintf (stderr,
119                   _("Try `%s ./%s' to remove the file %s.\n"),
120                   argv[0],
121                   quotearg_n_style (1, shell_quoting_style, arg),
122                   quote (arg));
123          break;
124        }
125    }
126}
127
128void
129usage (int status)
130{
131  if (status != EXIT_SUCCESS)
132    fprintf (stderr, _("Try `%s --help' for more information.\n"),
133             program_name);
134  else
135    {
136      printf (_("Usage: %s [OPTION]... FILE...\n"), program_name);
137      fputs (_("\
138Remove (unlink) the FILE(s).\n\
139\n\
140  -f, --force           ignore nonexistent files, never prompt\n\
141  -i                    prompt before every removal\n\
142"), stdout);
143      fputs (_("\
144  -I                    prompt once before removing more than three files, or\n\
145                          when removing recursively.  Less intrusive than -i,\n\
146                          while still giving protection against most mistakes\n\
147      --interactive[=WHEN]  prompt according to WHEN: never, once (-I), or\n\
148                          always (-i).  Without WHEN, prompt always\n\
149"), stdout);
150      fputs (_("\
151      --one-file-system  when removing a hierarchy recursively, skip any\n\
152                          directory that is on a file system different from\n\
153                          that of the corresponding command line argument\n\
154"), stdout);
155      fputs (_("\
156      --no-preserve-root  do not treat `/' specially\n\
157      --preserve-root   do not remove `/' (default)\n\
158  -r, -R, --recursive   remove directories and their contents recursively\n\
159  -v, --verbose         explain what is being done\n\
160"), stdout);
161      fputs (HELP_OPTION_DESCRIPTION, stdout);
162      fputs (VERSION_OPTION_DESCRIPTION, stdout);
163      fputs (_("\
164\n\
165By default, rm does not remove directories.  Use the --recursive (-r or -R)\n\
166option to remove each listed directory, too, along with all of its contents.\n\
167"), stdout);
168      printf (_("\
169\n\
170To remove a file whose name starts with a `-', for example `-foo',\n\
171use one of these commands:\n\
172  %s -- -foo\n\
173\n\
174  %s ./-foo\n\
175"),
176              program_name, program_name);
177      fputs (_("\
178\n\
179Note that if you use rm to remove a file, it is usually possible to recover\n\
180the contents of that file.  If you want more assurance that the contents are\n\
181truly unrecoverable, consider using shred.\n\
182"), stdout);
183      emit_ancillary_info ();
184    }
185  exit (status);
186}
187
188static void
189rm_option_init (struct rm_options *x)
190{
191  x->ignore_missing_files = false;
192  x->interactive = RMI_SOMETIMES;
193  x->one_file_system = false;
194  x->recursive = false;
195  x->root_dev_ino = NULL;
196  x->stdin_tty = isatty (STDIN_FILENO);
197  x->verbose = false;
198
199  /* Since this program exits immediately after calling `rm', rm need not
200     expend unnecessary effort to preserve the initial working directory.  */
201  x->require_restore_cwd = false;
202}
203
204int
205main (int argc, char **argv)
206{
207  bool preserve_root = true;
208  struct rm_options x;
209  bool prompt_once = false;
210  int c;
211
212  initialize_main (&argc, &argv);
213  set_program_name (argv[0]);
214  setlocale (LC_ALL, "");
215  bindtextdomain (PACKAGE, LOCALEDIR);
216  textdomain (PACKAGE);
217
218  atexit (close_stdin);
219
220  rm_option_init (&x);
221
222  /* Try to disable the ability to unlink a directory.  */
223  priv_set_remove_linkdir ();
224
225  while ((c = getopt_long (argc, argv, "dfirvIR", long_opts, NULL)) != -1)
226    {
227      switch (c)
228        {
229        case 'd':
230          /* Ignore this option, for backward compatibility with
231             coreutils 5.92.  FIXME: Some time after 2005, change this
232             to report an error (or perhaps behave like FreeBSD does)
233             instead of ignoring the option.  */
234          break;
235
236        case 'f':
237          x.interactive = RMI_NEVER;
238          x.ignore_missing_files = true;
239          prompt_once = false;
240          break;
241
242        case 'i':
243          x.interactive = RMI_ALWAYS;
244          x.ignore_missing_files = false;
245          prompt_once = false;
246          break;
247
248        case 'I':
249          x.interactive = RMI_NEVER;
250          x.ignore_missing_files = false;
251          prompt_once = true;
252          break;
253
254        case 'r':
255        case 'R':
256          x.recursive = true;
257          break;
258
259        case INTERACTIVE_OPTION:
260          {
261            int i;
262            if (optarg)
263              i = XARGMATCH ("--interactive", optarg, interactive_args,
264                             interactive_types);
265            else
266              i = interactive_always;
267            switch (i)
268              {
269              case interactive_never:
270                x.interactive = RMI_NEVER;
271                prompt_once = false;
272                break;
273
274              case interactive_once:
275                x.interactive = RMI_SOMETIMES;
276                x.ignore_missing_files = false;
277                prompt_once = true;
278                break;
279
280              case interactive_always:
281                x.interactive = RMI_ALWAYS;
282                x.ignore_missing_files = false;
283                prompt_once = false;
284                break;
285              }
286            break;
287          }
288
289        case ONE_FILE_SYSTEM:
290          x.one_file_system = true;
291          break;
292
293        case NO_PRESERVE_ROOT:
294          preserve_root = false;
295          break;
296
297        case PRESERVE_ROOT:
298          preserve_root = true;
299          break;
300
301        case PRESUME_INPUT_TTY_OPTION:
302          x.stdin_tty = true;
303          break;
304
305        case 'v':
306          x.verbose = true;
307          break;
308
309        case_GETOPT_HELP_CHAR;
310        case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
311        default:
312          diagnose_leading_hyphen (argc, argv);
313          usage (EXIT_FAILURE);
314        }
315    }
316
317  if (argc <= optind)
318    {
319      if (x.ignore_missing_files)
320        exit (EXIT_SUCCESS);
321      else
322        {
323          error (0, 0, _("missing operand"));
324          usage (EXIT_FAILURE);
325        }
326    }
327
328  if (x.recursive && preserve_root)
329    {
330      static struct dev_ino dev_ino_buf;
331      x.root_dev_ino = get_root_dev_ino (&dev_ino_buf);
332      if (x.root_dev_ino == NULL)
333        error (EXIT_FAILURE, errno, _("failed to get attributes of %s"),
334               quote ("/"));
335    }
336  {
337  size_t n_files = argc - optind;
338  char **file =  argv + optind;
339
340  if (prompt_once && (x.recursive || 3 < n_files))
341    {
342      fprintf (stderr,
343               (x.recursive
344                ? _("%s: remove all arguments recursively? ")
345                : _("%s: remove all arguments? ")),
346               program_name);
347      if (!yesno ())
348        exit (EXIT_SUCCESS);
349    }
350
351  {
352  enum RM_status status = rm (file, &x);
353  assert (VALID_STATUS (status));
354  exit (status == RM_ERROR ? EXIT_FAILURE : EXIT_SUCCESS);
355  }
356  }
357}
358