| 1 | /* `rm' file deletion utility for GNU.
|
|---|
| 2 | Copyright (C) 88, 90, 91, 1994-2005 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 2, or (at your option)
|
|---|
| 7 | 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, write to the Free Software Foundation,
|
|---|
| 16 | Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */
|
|---|
| 17 |
|
|---|
| 18 | /* Written by Paul Rubin, David MacKenzie, and Richard Stallman.
|
|---|
| 19 | Reworked to use chdir and avoid recursion by Jim Meyering. */
|
|---|
| 20 |
|
|---|
| 21 | /* Implementation overview:
|
|---|
| 22 |
|
|---|
| 23 | In the `usual' case, RM saves no state for directories it is processing.
|
|---|
| 24 | When a removal fails (either due to an error or to an interactive `no'
|
|---|
| 25 | reply), the failure is noted (see description of `ht' in remove.c's
|
|---|
| 26 | remove_cwd_entries function) so that when/if the containing directory
|
|---|
| 27 | is reopened, RM doesn't try to remove the entry again.
|
|---|
| 28 |
|
|---|
| 29 | RM may delete arbitrarily deep hierarchies -- even ones in which file
|
|---|
| 30 | names (from root to leaf) are longer than the system-imposed maximum.
|
|---|
| 31 | It does this by using chdir to change to each directory in turn before
|
|---|
| 32 | removing the entries in that directory.
|
|---|
| 33 |
|
|---|
| 34 | RM detects directory cycles lazily. See lib/cycle-check.c.
|
|---|
| 35 |
|
|---|
| 36 | RM is careful to avoid forming full file names whenever possible.
|
|---|
| 37 | A full file name is formed only when it is about to be used -- e.g.
|
|---|
| 38 | in a diagnostic or in an interactive-mode prompt.
|
|---|
| 39 |
|
|---|
| 40 | RM minimizes the number of lstat system calls it makes. On systems
|
|---|
| 41 | that have valid d_type data in directory entries, RM makes only one
|
|---|
| 42 | lstat call per command line argument -- regardless of the depth of
|
|---|
| 43 | the hierarchy. */
|
|---|
| 44 |
|
|---|
| 45 | #include <config.h>
|
|---|
| 46 | #include <stdio.h>
|
|---|
| 47 | #include <getopt.h>
|
|---|
| 48 | #include <sys/types.h>
|
|---|
| 49 | #include <assert.h>
|
|---|
| 50 |
|
|---|
| 51 | #include "system.h"
|
|---|
| 52 | #include "dirname.h"
|
|---|
| 53 | #include "error.h"
|
|---|
| 54 | #include "lstat.h"
|
|---|
| 55 | #include "quote.h"
|
|---|
| 56 | #include "quotearg.h"
|
|---|
| 57 | #include "remove.h"
|
|---|
| 58 | #include "root-dev-ino.h"
|
|---|
| 59 |
|
|---|
| 60 | /* The official name of this program (e.g., no `g' prefix). */
|
|---|
| 61 | #define PROGRAM_NAME "rm"
|
|---|
| 62 |
|
|---|
| 63 | #define AUTHORS \
|
|---|
| 64 | "Paul Rubin", "David MacKenzie, Richard Stallman", "Jim Meyering"
|
|---|
| 65 |
|
|---|
| 66 | /* Name this program was run with. */
|
|---|
| 67 | char *program_name;
|
|---|
| 68 |
|
|---|
| 69 | /* For long options that have no equivalent short option, use a
|
|---|
| 70 | non-character as a pseudo short option, starting with CHAR_MAX + 1. */
|
|---|
| 71 | enum
|
|---|
| 72 | {
|
|---|
| 73 | NO_PRESERVE_ROOT = CHAR_MAX + 1,
|
|---|
| 74 | PRESERVE_ROOT,
|
|---|
| 75 | PRESUME_INPUT_TTY_OPTION
|
|---|
| 76 | };
|
|---|
| 77 |
|
|---|
| 78 | static struct option const long_opts[] =
|
|---|
| 79 | {
|
|---|
| 80 | {"directory", no_argument, NULL, 'd'},
|
|---|
| 81 | {"force", no_argument, NULL, 'f'},
|
|---|
| 82 | {"interactive", no_argument, NULL, 'i'},
|
|---|
| 83 |
|
|---|
| 84 | {"no-preserve-root", no_argument, NULL, NO_PRESERVE_ROOT},
|
|---|
| 85 | {"preserve-root", no_argument, NULL, PRESERVE_ROOT},
|
|---|
| 86 |
|
|---|
| 87 | /* This is solely for testing. Do not document. */
|
|---|
| 88 | /* It is relatively difficult to ensure that there is a tty on stdin.
|
|---|
| 89 | Since rm acts differently depending on that, without this option,
|
|---|
| 90 | it'd be harder to test the parts of rm that depend on that setting. */
|
|---|
| 91 | {"presume-input-tty", no_argument, NULL, PRESUME_INPUT_TTY_OPTION},
|
|---|
| 92 |
|
|---|
| 93 | {"recursive", no_argument, NULL, 'r'},
|
|---|
| 94 | {"verbose", no_argument, NULL, 'v'},
|
|---|
| 95 | {GETOPT_HELP_OPTION_DECL},
|
|---|
| 96 | {GETOPT_VERSION_OPTION_DECL},
|
|---|
| 97 | {NULL, 0, NULL, 0}
|
|---|
| 98 | };
|
|---|
| 99 |
|
|---|
| 100 | /* Advise the user about invalid usages like "rm -foo" if the file
|
|---|
| 101 | "-foo" exists, assuming ARGC and ARGV are as with `main'. */
|
|---|
| 102 |
|
|---|
| 103 | static void
|
|---|
| 104 | diagnose_leading_hyphen (int argc, char **argv)
|
|---|
| 105 | {
|
|---|
| 106 | /* OPTIND is unreliable, so iterate through the arguments looking
|
|---|
| 107 | for a file name that looks like an option. */
|
|---|
| 108 | int i;
|
|---|
| 109 |
|
|---|
| 110 | for (i = 1; i < argc; i++)
|
|---|
| 111 | {
|
|---|
| 112 | char const *arg = argv[i];
|
|---|
| 113 | struct stat st;
|
|---|
| 114 |
|
|---|
| 115 | if (arg[0] == '-' && arg[1] && lstat (arg, &st) == 0)
|
|---|
| 116 | {
|
|---|
| 117 | fprintf (stderr,
|
|---|
| 118 | _("Try `%s ./%s' to remove the file %s.\n"),
|
|---|
| 119 | argv[0],
|
|---|
| 120 | quotearg_n_style (1, shell_quoting_style, arg),
|
|---|
| 121 | quote (arg));
|
|---|
| 122 | break;
|
|---|
| 123 | }
|
|---|
| 124 | }
|
|---|
| 125 | }
|
|---|
| 126 |
|
|---|
| 127 | void
|
|---|
| 128 | usage (int status)
|
|---|
| 129 | {
|
|---|
| 130 | if (status != EXIT_SUCCESS)
|
|---|
| 131 | fprintf (stderr, _("Try `%s --help' for more information.\n"),
|
|---|
| 132 | program_name);
|
|---|
| 133 | else
|
|---|
| 134 | {
|
|---|
| 135 | char *base = base_name (program_name);
|
|---|
| 136 | printf (_("Usage: %s [OPTION]... FILE...\n"), program_name);
|
|---|
| 137 | fputs (_("\
|
|---|
| 138 | Remove (unlink) the FILE(s).\n\
|
|---|
| 139 | \n\
|
|---|
| 140 | -d, --directory unlink FILE, even if it is a non-empty directory\n\
|
|---|
| 141 | (super-user only; this works only if your system\n\
|
|---|
| 142 | supports `unlink' for nonempty directories)\n\
|
|---|
| 143 | -f, --force ignore nonexistent files, never prompt\n\
|
|---|
| 144 | -i, --interactive prompt before any removal\n\
|
|---|
| 145 | "), stdout);
|
|---|
| 146 | fputs (_("\
|
|---|
| 147 | --no-preserve-root do not treat `/' specially (the default)\n\
|
|---|
| 148 | --preserve-root fail to operate recursively on `/'\n\
|
|---|
| 149 | -r, -R, --recursive remove directories and their contents recursively\n\
|
|---|
| 150 | -v, --verbose explain what is being done\n\
|
|---|
| 151 | "), stdout);
|
|---|
| 152 | fputs (HELP_OPTION_DESCRIPTION, stdout);
|
|---|
| 153 | fputs (VERSION_OPTION_DESCRIPTION, stdout);
|
|---|
| 154 | fputs (_("\
|
|---|
| 155 | \n\
|
|---|
| 156 | By default, rm does not remove directories. Use the --recursive (-r or -R)\n\
|
|---|
| 157 | option to remove each listed directory, too, along with all of its contents.\n\
|
|---|
| 158 | "), stdout);
|
|---|
| 159 | printf (_("\
|
|---|
| 160 | \n\
|
|---|
| 161 | To remove a file whose name starts with a `-', for example `-foo',\n\
|
|---|
| 162 | use one of these commands:\n\
|
|---|
| 163 | %s -- -foo\n\
|
|---|
| 164 | \n\
|
|---|
| 165 | %s ./-foo\n\
|
|---|
| 166 | "),
|
|---|
| 167 | base, base);
|
|---|
| 168 | fputs (_("\
|
|---|
| 169 | \n\
|
|---|
| 170 | Note that if you use rm to remove a file, it is usually possible to recover\n\
|
|---|
| 171 | the contents of that file. If you want more assurance that the contents are\n\
|
|---|
| 172 | truly unrecoverable, consider using shred.\n\
|
|---|
| 173 | "), stdout);
|
|---|
| 174 | printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT);
|
|---|
| 175 | }
|
|---|
| 176 | exit (status);
|
|---|
| 177 | }
|
|---|
| 178 |
|
|---|
| 179 | static void
|
|---|
| 180 | rm_option_init (struct rm_options *x)
|
|---|
| 181 | {
|
|---|
| 182 | x->unlink_dirs = false;
|
|---|
| 183 | x->ignore_missing_files = false;
|
|---|
| 184 | x->interactive = false;
|
|---|
| 185 | x->recursive = false;
|
|---|
| 186 | x->root_dev_ino = NULL;
|
|---|
| 187 | x->stdin_tty = isatty (STDIN_FILENO);
|
|---|
| 188 | x->verbose = false;
|
|---|
| 189 |
|
|---|
| 190 | /* Since this program exits immediately after calling `rm', rm need not
|
|---|
| 191 | expend unnecessary effort to preserve the initial working directory. */
|
|---|
| 192 | x->require_restore_cwd = false;
|
|---|
| 193 | }
|
|---|
| 194 |
|
|---|
| 195 | int
|
|---|
| 196 | main (int argc, char **argv)
|
|---|
| 197 | {
|
|---|
| 198 | bool preserve_root = false;
|
|---|
| 199 | struct rm_options x;
|
|---|
| 200 | int c;
|
|---|
| 201 |
|
|---|
| 202 | initialize_main (&argc, &argv);
|
|---|
| 203 | program_name = argv[0];
|
|---|
| 204 | setlocale (LC_ALL, "");
|
|---|
| 205 | bindtextdomain (PACKAGE, LOCALEDIR);
|
|---|
| 206 | textdomain (PACKAGE);
|
|---|
| 207 |
|
|---|
| 208 | atexit (close_stdout);
|
|---|
| 209 |
|
|---|
| 210 | rm_option_init (&x);
|
|---|
| 211 |
|
|---|
| 212 | while ((c = getopt_long (argc, argv, "dfirvR", long_opts, NULL)) != -1)
|
|---|
| 213 | {
|
|---|
| 214 | switch (c)
|
|---|
| 215 | {
|
|---|
| 216 | case 'd':
|
|---|
| 217 | x.unlink_dirs = true;
|
|---|
| 218 | break;
|
|---|
| 219 |
|
|---|
| 220 | case 'f':
|
|---|
| 221 | x.interactive = false;
|
|---|
| 222 | x.ignore_missing_files = true;
|
|---|
| 223 | break;
|
|---|
| 224 |
|
|---|
| 225 | case 'i':
|
|---|
| 226 | x.interactive = true;
|
|---|
| 227 | x.ignore_missing_files = false;
|
|---|
| 228 | break;
|
|---|
| 229 |
|
|---|
| 230 | case 'r':
|
|---|
| 231 | case 'R':
|
|---|
| 232 | x.recursive = true;
|
|---|
| 233 | break;
|
|---|
| 234 |
|
|---|
| 235 | case NO_PRESERVE_ROOT:
|
|---|
| 236 | preserve_root = false;
|
|---|
| 237 | break;
|
|---|
| 238 |
|
|---|
| 239 | case PRESERVE_ROOT:
|
|---|
| 240 | preserve_root = true;
|
|---|
| 241 | break;
|
|---|
| 242 |
|
|---|
| 243 | case PRESUME_INPUT_TTY_OPTION:
|
|---|
| 244 | x.stdin_tty = true;
|
|---|
| 245 | break;
|
|---|
| 246 |
|
|---|
| 247 | case 'v':
|
|---|
| 248 | x.verbose = true;
|
|---|
| 249 | break;
|
|---|
| 250 |
|
|---|
| 251 | case_GETOPT_HELP_CHAR;
|
|---|
| 252 | case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
|
|---|
| 253 | default:
|
|---|
| 254 | diagnose_leading_hyphen (argc, argv);
|
|---|
| 255 | usage (EXIT_FAILURE);
|
|---|
| 256 | }
|
|---|
| 257 | }
|
|---|
| 258 |
|
|---|
| 259 | if (argc <= optind)
|
|---|
| 260 | {
|
|---|
| 261 | if (x.ignore_missing_files)
|
|---|
| 262 | exit (EXIT_SUCCESS);
|
|---|
| 263 | else
|
|---|
| 264 | {
|
|---|
| 265 | error (0, 0, _("missing operand"));
|
|---|
| 266 | usage (EXIT_FAILURE);
|
|---|
| 267 | }
|
|---|
| 268 | }
|
|---|
| 269 |
|
|---|
| 270 | if (x.recursive & preserve_root)
|
|---|
| 271 | {
|
|---|
| 272 | static struct dev_ino dev_ino_buf;
|
|---|
| 273 | x.root_dev_ino = get_root_dev_ino (&dev_ino_buf);
|
|---|
| 274 | if (x.root_dev_ino == NULL)
|
|---|
| 275 | error (EXIT_FAILURE, errno, _("failed to get attributes of %s"),
|
|---|
| 276 | quote ("/"));
|
|---|
| 277 | }
|
|---|
| 278 |
|
|---|
| 279 | {
|
|---|
| 280 | size_t n_files = argc - optind;
|
|---|
| 281 | char const *const *file = (char const *const *) argv + optind;
|
|---|
| 282 |
|
|---|
| 283 | enum RM_status status = rm (n_files, file, &x);
|
|---|
| 284 | assert (VALID_STATUS (status));
|
|---|
| 285 | exit (status == RM_ERROR ? EXIT_FAILURE : EXIT_SUCCESS);
|
|---|
| 286 | }
|
|---|
| 287 | }
|
|---|