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