1 /* 'rm' file deletion utility for GNU.
2    Copyright (C) 1988-2023 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 <https://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 
26 #include "system.h"
27 #include "argmatch.h"
28 #include "assure.h"
29 #include "remove.h"
30 #include "root-dev-ino.h"
31 #include "yesno.h"
32 #include "priv-set.h"
33 
34 /* The official name of this program (e.g., no 'g' prefix).  */
35 #define PROGRAM_NAME "rm"
36 
37 #define AUTHORS \
38   proper_name ("Paul Rubin"), \
39   proper_name ("David MacKenzie"), \
40   proper_name ("Richard M. Stallman"), \
41   proper_name ("Jim Meyering")
42 
43 /* For long options that have no equivalent short option, use a
44    non-character as a pseudo short option, starting with CHAR_MAX + 1.  */
45 enum
46 {
47   INTERACTIVE_OPTION = CHAR_MAX + 1,
48   ONE_FILE_SYSTEM,
49   NO_PRESERVE_ROOT,
50   PRESERVE_ROOT,
51   PRESUME_INPUT_TTY_OPTION
52 };
53 
54 enum interactive_type
55   {
56     interactive_never,		/* 0: no option or --interactive=never */
57     interactive_once,		/* 1: -I or --interactive=once */
58     interactive_always		/* 2: default, -i or --interactive=always */
59   };
60 
61 static struct option const long_opts[] =
62 {
63   {"force", no_argument, nullptr, 'f'},
64   {"interactive", optional_argument, nullptr, INTERACTIVE_OPTION},
65 
66   {"one-file-system", no_argument, nullptr, ONE_FILE_SYSTEM},
67   {"no-preserve-root", no_argument, nullptr, NO_PRESERVE_ROOT},
68   {"preserve-root", optional_argument, nullptr, PRESERVE_ROOT},
69 
70   /* This is solely for testing.  Do not document.  */
71   /* It is relatively difficult to ensure that there is a tty on stdin.
72      Since rm acts differently depending on that, without this option,
73      it'd be harder to test the parts of rm that depend on that setting.  */
74   {"-presume-input-tty", no_argument, nullptr, PRESUME_INPUT_TTY_OPTION},
75 
76   {"recursive", no_argument, nullptr, 'r'},
77   {"dir", no_argument, nullptr, 'd'},
78   {"verbose", no_argument, nullptr, 'v'},
79   {GETOPT_HELP_OPTION_DECL},
80   {GETOPT_VERSION_OPTION_DECL},
81   {nullptr, 0, nullptr, 0}
82 };
83 
84 static char const *const interactive_args[] =
85 {
86   "never", "no", "none",
87   "once",
88   "always", "yes", nullptr
89 };
90 static enum interactive_type const interactive_types[] =
91 {
92   interactive_never, interactive_never, interactive_never,
93   interactive_once,
94   interactive_always, interactive_always
95 };
96 ARGMATCH_VERIFY (interactive_args, interactive_types);
97 
98 /* Advise the user about invalid usages like "rm -foo" if the file
99    "-foo" exists, assuming ARGC and ARGV are as with 'main'.  */
100 
101 static void
diagnose_leading_hyphen(int argc,char ** argv)102 diagnose_leading_hyphen (int argc, char **argv)
103 {
104   /* OPTIND is unreliable, so iterate through the arguments looking
105      for a file name that looks like an option.  */
106 
107   for (int i = 1; i < argc; i++)
108     {
109       char const *arg = argv[i];
110       struct stat st;
111 
112       if (arg[0] == '-' && arg[1] && lstat (arg, &st) == 0)
113         {
114           fprintf (stderr,
115                    _("Try '%s ./%s' to remove the file %s.\n"),
116                    argv[0],
117                    quotearg_n_style (1, shell_escape_quoting_style, arg),
118                    quoteaf (arg));
119           break;
120         }
121     }
122 }
123 
124 void
usage(int status)125 usage (int status)
126 {
127   if (status != EXIT_SUCCESS)
128     emit_try_help ();
129   else
130     {
131       printf (_("Usage: %s [OPTION]... [FILE]...\n"), program_name);
132       fputs (_("\
133 Remove (unlink) the FILE(s).\n\
134 \n\
135   -f, --force           ignore nonexistent files and arguments, never prompt\n\
136   -i                    prompt before every removal\n\
137 "), stdout);
138       fputs (_("\
139   -I                    prompt once before removing more than three files, or\n\
140                           when removing recursively; less intrusive than -i,\n\
141                           while still giving protection against most mistakes\n\
142       --interactive[=WHEN]  prompt according to WHEN: never, once (-I), or\n\
143                           always (-i); without WHEN, prompt always\n\
144 "), stdout);
145       fputs (_("\
146       --one-file-system  when removing a hierarchy recursively, skip any\n\
147                           directory that is on a file system different from\n\
148                           that of the corresponding command line argument\n\
149 "), stdout);
150       fputs (_("\
151       --no-preserve-root  do not treat '/' specially\n\
152       --preserve-root[=all]  do not remove '/' (default);\n\
153                               with 'all', reject any command line argument\n\
154                               on a separate device from its parent\n\
155 "), stdout);
156       fputs (_("\
157   -r, -R, --recursive   remove directories and their contents recursively\n\
158   -d, --dir             remove empty directories\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\
165 By default, rm does not remove directories.  Use the --recursive (-r or -R)\n\
166 option to remove each listed directory, too, along with all of its contents.\n\
167 "), stdout);
168       fputs (_("\
169 \n\
170 Any attempt to remove a file whose last file name component is '.' or '..'\n\
171 is rejected with a diagnostic.\n\
172 "), stdout);
173       printf (_("\
174 \n\
175 To remove a file whose name starts with a '-', for example '-foo',\n\
176 use one of these commands:\n\
177   %s -- -foo\n\
178 \n\
179   %s ./-foo\n\
180 "),
181               program_name, program_name);
182       fputs (_("\
183 \n\
184 Note that if you use rm to remove a file, it might be possible to recover\n\
185 some of its contents, given sufficient expertise and/or time.  For greater\n\
186 assurance that the contents are truly unrecoverable, consider using shred(1).\n\
187 "), stdout);
188       emit_ancillary_info (PROGRAM_NAME);
189     }
190   exit (status);
191 }
192 
193 static void
rm_option_init(struct rm_options * x)194 rm_option_init (struct rm_options *x)
195 {
196   x->ignore_missing_files = false;
197   x->interactive = RMI_SOMETIMES;
198   x->one_file_system = false;
199   x->remove_empty_directories = false;
200   x->recursive = false;
201   x->root_dev_ino = nullptr;
202   x->preserve_all_root = false;
203   x->stdin_tty = isatty (STDIN_FILENO);
204   x->verbose = false;
205 
206   /* Since this program exits immediately after calling 'rm', rm need not
207      expend unnecessary effort to preserve the initial working directory.  */
208   x->require_restore_cwd = false;
209 }
210 
211 int
main(int argc,char ** argv)212 main (int argc, char **argv)
213 {
214   bool preserve_root = true;
215   struct rm_options x;
216   bool prompt_once = false;
217   int c;
218 
219   initialize_main (&argc, &argv);
220   set_program_name (argv[0]);
221   setlocale (LC_ALL, "");
222   bindtextdomain (PACKAGE, LOCALEDIR);
223   textdomain (PACKAGE);
224 
225   atexit (close_stdin);
226 
227   rm_option_init (&x);
228 
229   /* Try to disable the ability to unlink a directory.  */
230   priv_set_remove_linkdir ();
231 
232   while ((c = getopt_long (argc, argv, "dfirvIR", long_opts, nullptr)) != -1)
233     {
234       switch (c)
235         {
236         case 'd':
237           x.remove_empty_directories = true;
238           break;
239 
240         case 'f':
241           x.interactive = RMI_NEVER;
242           x.ignore_missing_files = true;
243           prompt_once = false;
244           break;
245 
246         case 'i':
247           x.interactive = RMI_ALWAYS;
248           x.ignore_missing_files = false;
249           prompt_once = false;
250           break;
251 
252         case 'I':
253           x.interactive = RMI_SOMETIMES;
254           x.ignore_missing_files = false;
255           prompt_once = true;
256           break;
257 
258         case 'r':
259         case 'R':
260           x.recursive = true;
261           break;
262 
263         case INTERACTIVE_OPTION:
264           {
265             int i;
266             if (optarg)
267               i = XARGMATCH ("--interactive", optarg, interactive_args,
268                              interactive_types);
269             else
270               i = interactive_always;
271             switch (i)
272               {
273               case interactive_never:
274                 x.interactive = RMI_NEVER;
275                 prompt_once = false;
276                 break;
277 
278               case interactive_once:
279                 x.interactive = RMI_SOMETIMES;
280                 x.ignore_missing_files = false;
281                 prompt_once = true;
282                 break;
283 
284               case interactive_always:
285                 x.interactive = RMI_ALWAYS;
286                 x.ignore_missing_files = false;
287                 prompt_once = false;
288                 break;
289               }
290             break;
291           }
292 
293         case ONE_FILE_SYSTEM:
294           x.one_file_system = true;
295           break;
296 
297         case NO_PRESERVE_ROOT:
298           if (! STREQ (argv[optind - 1], "--no-preserve-root"))
299             error (EXIT_FAILURE, 0,
300                    _("you may not abbreviate the --no-preserve-root option"));
301           preserve_root = false;
302           break;
303 
304         case PRESERVE_ROOT:
305           if (optarg)
306             {
307               if STREQ (optarg, "all")
308                 x.preserve_all_root = true;
309               else
310                 error (EXIT_FAILURE, 0,
311                        _("unrecognized --preserve-root argument: %s"),
312                        quoteaf (optarg));
313             }
314           preserve_root = true;
315           break;
316 
317         case PRESUME_INPUT_TTY_OPTION:
318           x.stdin_tty = true;
319           break;
320 
321         case 'v':
322           x.verbose = true;
323           break;
324 
325         case_GETOPT_HELP_CHAR;
326         case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
327         default:
328           diagnose_leading_hyphen (argc, argv);
329           usage (EXIT_FAILURE);
330         }
331     }
332 
333   if (argc <= optind)
334     {
335       if (x.ignore_missing_files)
336         return EXIT_SUCCESS;
337       else
338         {
339           error (0, 0, _("missing operand"));
340           usage (EXIT_FAILURE);
341         }
342     }
343 
344   if (x.recursive && preserve_root)
345     {
346       static struct dev_ino dev_ino_buf;
347       x.root_dev_ino = get_root_dev_ino (&dev_ino_buf);
348       if (x.root_dev_ino == nullptr)
349         error (EXIT_FAILURE, errno, _("failed to get attributes of %s"),
350                quoteaf ("/"));
351     }
352 
353   uintmax_t n_files = argc - optind;
354   char **file =  argv + optind;
355 
356   if (prompt_once && (x.recursive || 3 < n_files))
357     {
358       fprintf (stderr,
359                (x.recursive
360                 ? ngettext ("%s: remove %ju argument recursively? ",
361                             "%s: remove %ju arguments recursively? ",
362                             select_plural (n_files))
363                 : ngettext ("%s: remove %ju argument? ",
364                             "%s: remove %ju arguments? ",
365                             select_plural (n_files))),
366                program_name, n_files);
367       if (!yesno ())
368         return EXIT_SUCCESS;
369     }
370 
371   enum RM_status status = rm (file, &x);
372   affirm (VALID_STATUS (status));
373   return status == RM_ERROR ? EXIT_FAILURE : EXIT_SUCCESS;
374 }
375