1 /* `rm' file deletion utility for GNU.
2 Copyright (C) 88, 90, 91, 94, 95, 96, 1997 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 2, or (at your option)
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, write to the Free Software Foundation,
16 Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */
18 /* Written by Paul Rubin, David MacKenzie, and Richard Stallman.
19 Reworked to use chdir and hash tables by Jim Meyering. */
24 #include <sys/types.h>
34 # if defined (__GNUC__) || __STDC__
35 # define PARAMS(args) args
37 # define PARAMS(args) ()
41 #define obstack_chunk_alloc malloc
42 #define obstack_chunk_free free
44 #ifdef D_INO_IN_DIRENT
45 # define D_INO(dp) ((dp)->d_ino)
47 /* Some systems don't have inodes, so fake them to avoid lots of ifdefs. */
51 #if !defined (S_ISLNK)
52 # define S_ISLNK(Mode) 0
55 #define DOT_OR_DOTDOT(Basename) \
56 (Basename[0] == '.' && (Basename[1] == '\0' \
57 || (Basename[1] == '.' && Basename[2] == '\0')))
60 # define ASSIGN_STRDUPA(DEST, S) \
61 do { DEST = strdupa(S); } while (0)
63 # define ASSIGN_STRDUPA(DEST, S) \
66 size_t len_ = strlen (S) + 1; \
67 char *tmp_dest_ = alloca (len_); \
68 memcpy (tmp_dest_, (S), len_); \
76 /* FIXME: describe and explain ordering: `ok' increasing in seriousness. */
82 #define VALID_STATUS(S) \
83 ((S) == RM_OK || (S) == RM_USER_DECLINED || (S) == RM_ERROR)
85 /* Initial capacity of per-directory hash table of entries that have
86 been processed but not been deleted. */
87 #define HT_INITIAL_CAPACITY 13
89 /* Initial capacity of the active directory hash table. This table will
90 be resized only for hierarchies more than about 45 levels deep. */
91 #define ACTIVE_DIR_INITIAL_CAPACITY 53
96 unsigned int have_filetype_mode:1;
97 unsigned int have_full_mode:1;
111 void strip_trailing_slashes ();
116 /* Forward dcl for recursively called function. */
117 static enum RM_status rm PARAMS ((struct File_spec *fs,
118 int user_specified_name));
120 /* Name this program was run with. */
123 /* If nonzero, display the name of each file removed. */
126 /* If nonzero, ignore nonexistant files. */
127 static int ignore_missing_files;
129 /* If nonzero, recursively remove directories. */
130 static int recursive;
132 /* If nonzero, query the user about whether to remove each file. */
133 static int interactive;
135 /* If nonzero, remove directories with unlink instead of rmdir, and don't
136 require a directory to be empty before trying to unlink it.
137 Only works for the super-user. */
138 static int unlink_dirs;
140 /* If nonzero, stdin is a tty. */
141 static int stdin_tty;
143 /* If nonzero, display usage information and exit. */
144 static int show_help;
146 /* If nonzero, print the version on standard output and exit. */
147 static int show_version;
149 /* FIXME: describe */
150 static struct obstack dir_stack;
152 /* Stack of lengths of directory names (including trailing slash)
153 appended to dir_stack. We have to have a separate stack of lengths
154 (rather than just popping back to previous slash) because the first
155 element pushed onto the dir stack may contain slashes. */
156 static struct obstack len_stack;
158 /* Set of `active' directories from the current command-line parameter
159 to the level in the hierarchy at which files are being removed.
160 A directory is added to the active set when RM begins removing it
161 (or its entries), and it is removed from the set just after RM has
162 finished processing it.
164 This is actually a map (not a set), implemented with a hash table.
165 For each active directory, it maps the directory's inode number to the
166 depth of that directory relative to the root of the tree being deleted.
167 A directory specified on the command line has depth zero.
168 This construct is used to detect directory cycles so that RM can warn
169 about them rather than iterating endlessly. */
170 static struct hash_table *active_dir_map;
172 /* An entry in the active_dir_map. */
173 struct active_dir_ent
179 static struct option const long_opts[] =
181 {"directory", no_argument, &unlink_dirs, 1},
182 {"force", no_argument, NULL, 'f'},
183 {"interactive", no_argument, NULL, 'i'},
184 {"recursive", no_argument, &recursive, 1},
185 {"verbose", no_argument, &verbose, 1},
186 {"help", no_argument, &show_help, 1},
187 {"version", no_argument, &show_version, 1},
191 static inline unsigned int
194 return obstack_object_size (&len_stack) / sizeof (size_t);
198 print_nth_dir (FILE *stream, unsigned int depth)
200 size_t *length = (size_t *) obstack_base (&len_stack);
201 char *dir_name = (char *) obstack_base (&dir_stack);
202 unsigned int sum = 0;
205 assert (0 <= depth && depth < current_depth ());
207 for (i = 0; i <= depth; i++)
212 fwrite (dir_name, 1, sum, stream);
215 static inline struct active_dir_ent *
216 make_active_dir_ent (ino_t inum, unsigned int depth)
218 struct active_dir_ent *ent;
219 ent = (struct active_dir_ent *) xmalloc (sizeof *ent);
226 hash_active_dir_ent_1 (void const *x)
228 struct active_dir_ent const *ade = x;
229 return_INTEGER_HASH_1 (ade->inum);
233 hash_active_dir_ent_2 (void const *x)
235 struct active_dir_ent const *ade = x;
236 return_INTEGER_HASH_2 (ade->inum);
240 hash_compare_active_dir_ents (void const *x, void const *y)
242 struct active_dir_ent const *a = x;
243 struct active_dir_ent const *b = y;
244 return_INTEGER_COMPARE (a->inum, b->inum);
248 hash_string_1 (void const *x)
250 return_STRING_HASH_1 (x);
254 hash_string_2 (void const *x)
256 return_STRING_HASH_2 (x);
260 hash_compare_strings (void const *x, void const *y)
262 return strcmp (x, y);
269 fprintf (stderr, _("Try `%s --help' for more information.\n"),
273 printf (_("Usage: %s [OPTION]... FILE...\n"), program_name);
275 Remove (unlink) the FILE(s).\n\
277 -d, --directory unlink directory, even if non-empty (super-user only)\n\
278 -f, --force ignore nonexistent files, never prompt\n\
279 -i, --interactive prompt before any removal\n\
280 -r, -R, --recursive remove the contents of directories recursively\n\
281 -v, --verbose explain what is being done\n\
282 --help display this help and exit\n\
283 --version output version information and exit\n\
285 puts (_("\nReport bugs to <fileutils-bugs@gnu.ai.mit.edu>."));
291 push_dir (const char *dir_name)
295 len = strlen (dir_name);
297 /* Append the string onto the stack. */
298 obstack_grow (&dir_stack, dir_name, len);
300 /* Append a trailing slash. */
301 obstack_1grow (&dir_stack, '/');
303 /* Add one for the slash. */
306 /* Push the length (including slash) onto its stack. */
307 obstack_grow (&len_stack, &len, sizeof (len));
313 int n_lengths = obstack_object_size (&len_stack) / sizeof (size_t);
314 size_t *length = (size_t *) obstack_base (&len_stack);
317 assert (n_lengths > 0);
318 top_len = length[n_lengths - 1];
319 assert (top_len >= 2);
321 /* Pop off the specified length of pathname. */
322 assert (obstack_object_size (&dir_stack) >= top_len);
323 obstack_blank (&dir_stack, -top_len);
325 /* Pop the length stack, too. */
326 assert (obstack_object_size (&len_stack) >= sizeof (size_t));
327 obstack_blank (&len_stack, -(sizeof (size_t)));
330 /* Copy the SRC_LEN bytes of data beginning at SRC into the DST_LEN-byte
331 buffer, DST, so that the last source byte is at the end of the destination
332 buffer. If SRC_LEN is longer than DST_LEN, then set *TRUNCATED to non-zero.
333 Set *RESULT to point to the beginning of (the portion of) the source data
334 in DST. Return the number of bytes remaining in the destination buffer. */
337 right_justify (char *dst, size_t dst_len, const char *src, size_t src_len,
338 char **result, int *truncated)
343 if (src_len <= dst_len)
346 dp = dst + (dst_len - src_len);
351 sp = src + (src_len - dst_len);
357 memcpy (dp, sp, src_len);
360 return dst_len - src_len;
363 /* Using the global directory name obstack, create the full path to FILENAME.
364 Return it in sometimes-realloc'd space that should not be freed by the
365 caller. Realloc as necessary. If realloc fails, use a static buffer
366 and put as long a suffix in that buffer as possible. */
369 full_filename (const char *filename)
371 static char *buf = NULL;
372 static size_t n_allocated = 0;
374 int dir_len = obstack_object_size (&dir_stack);
375 char *dir_name = (char *) obstack_base (&dir_stack);
376 size_t n_bytes_needed;
379 filename_len = strlen (filename);
380 n_bytes_needed = dir_len + filename_len + 1;
382 if (n_bytes_needed > n_allocated)
384 /* FIXME: use realloc, not xrealloc. */
385 /* But be sure realloc accepts NULL first arg.
386 FIXME: replace with rpl_realloc if not. */
387 /* This funciton can't use xrealloc. Otherwise, out-of-memory
388 errors involving a file name to be expanded here wouldn't ever
389 be issued. Use realloc and fall back on using a static buffer
390 if memory is a problem. */
391 buf = xrealloc (buf, n_bytes_needed);
392 n_allocated = n_bytes_needed;
396 #define SBUF_SIZE 512
397 #define ELLIPSES_PREFIX "[...]"
398 static char static_buf[SBUF_SIZE];
403 len = right_justify (static_buf, SBUF_SIZE, filename,
404 filename_len + 1, &p, &truncated);
405 right_justify (static_buf, len, dir_name, dir_len, &p, &truncated);
408 memcpy (static_buf, ELLIPSES_PREFIX,
409 sizeof (ELLIPSES_PREFIX) - 1);
415 /* Copy directory part, including trailing slash. */
416 memcpy (buf, dir_name, dir_len);
418 /* Append filename part, including trailing zero byte. */
419 memcpy (buf + dir_len, filename, filename_len + 1);
421 assert (strlen (buf) + 1 == n_bytes_needed);
427 fspec_init_file (struct File_spec *fs, const char *filename)
429 fs->filename = (char *) filename;
430 fs->have_full_mode = 0;
431 fs->have_filetype_mode = 0;
435 fspec_init_dp (struct File_spec *fs, struct dirent *dp)
437 fs->filename = dp->d_name;
438 fs->have_full_mode = 0;
439 fs->have_filetype_mode = 0;
440 fs->inum = D_INO (dp);
442 #if D_TYPE_IN_DIRENT && defined (DT_UNKNOWN)
443 if (filetype_mode != DT_UNKNOWN)
445 fs->have_filetype_mode = 1;
446 fs->mode = filetype_mode;
452 fspec_get_full_mode (struct File_spec *fs, mode_t *full_mode)
454 struct stat stat_buf;
456 if (fs->have_full_mode)
458 *full_mode = fs->mode;
462 if (lstat (fs->filename, &stat_buf))
465 fs->have_full_mode = 1;
466 fs->have_filetype_mode = 1;
467 fs->mode = stat_buf.st_mode;
468 fs->inum = stat_buf.st_ino;
470 *full_mode = stat_buf.st_mode;
475 fspec_get_filetype_mode (struct File_spec *fs, mode_t *filetype_mode)
479 if (fs->have_filetype_mode)
481 *filetype_mode = fs->mode;
486 fail = fspec_get_full_mode (fs, filetype_mode);
493 fspec_filetype_mode (const struct File_spec *fs)
495 assert (fs->have_filetype_mode);
499 /* Recursively remove all of the entries in the current directory.
500 Return an indication of the success of the operation. */
503 remove_cwd_entries (void)
505 /* NOTE: this is static. */
506 static DIR *dirp = NULL;
508 /* NULL or a malloc'd and initialized hash table of entries in the
509 current directory that have been processed but not removed --
510 due either to an error or to an interactive `no' response. */
511 struct hash_table *ht = NULL;
514 enum RM_status status = RM_OK;
520 /* FIXME-someday: but this is actually the previously opened dir. */
521 error (0, errno, "%s", full_filename ("."));
529 dirp = opendir (".");
532 if (errno != ENOENT || !ignore_missing_files)
534 error (0, errno, "%s", full_filename ("."));
540 while ((dp = readdir (dirp)) != NULL)
542 /* Skip this entry if it's `.' or `..'. */
543 if (DOT_OR_DOTDOT (dp->d_name))
546 /* Skip this entry if it's in the table of ones we've already
548 if (ht && hash_find_item (ht, (dp)->d_name))
553 enum RM_status tmp_status;
554 fspec_init_dp (&fs, dp);
555 tmp_status = rm (&fs, 0);
557 if (tmp_status > status)
559 assert (VALID_STATUS (status));
562 /* If this entry was not removed (due either to an error or to
563 an interactive `no' response), record it in the hash table so
564 we don't consider it again if we reopen this directory later. */
573 ht = hash_init_table (NULL, HT_INITIAL_CAPACITY, 0, 0,
574 hash_string_1, hash_string_2,
575 hash_compare_strings);
577 error (1, 0, _("Memory exhausted"));
579 p = xmalloc (NLENGTH (dp) + 1);
580 stpncpy (p, (dp)->d_name, NLENGTH (dp));
581 fail = hash_insert_item (ht, p, &old_item);
582 assert (old_item == NULL);
584 error (1, 0, _("Memory exhausted"));
591 while (dirp == NULL);
595 error (0, errno, "%s", full_filename ("."));
602 hash_free_items (ht);
603 hash_free_table (ht);
610 /* Query the user if appropriate, and if ok try to remove the
611 file or directory specified by FS. Return RM_OK if it is removed,
612 and RM_ERROR or RM_USER_DECLINED if not.
613 FIXME: describe IS_DIR parameter. */
615 static enum RM_status
616 remove_file (struct File_spec *fs)
619 char *pathname = fs->filename;
621 if (!ignore_missing_files && (interactive || stdin_tty)
622 && euidaccess (pathname, W_OK) )
624 if (!S_ISLNK (fspec_filetype_mode (fs)))
627 (S_ISDIR (fspec_filetype_mode (fs))
628 ? _("remove write-protected directory `%s'? ")
629 : _("remove write-protected file `%s'? ")),
630 full_filename (pathname));
632 return RM_USER_DECLINED;
638 if (!asked && interactive)
641 (S_ISDIR (fspec_filetype_mode (fs))
642 ? _("remove directory `%s'? ")
643 : _("remove `%s'? ")),
644 full_filename (pathname));
646 return RM_USER_DECLINED;
650 printf ("%s\n", full_filename (pathname));
652 if (unlink (pathname) && (errno != ENOENT || !ignore_missing_files))
654 error (0, errno, _("cannot unlink `%s'"), full_filename (pathname));
660 /* If not in recursive mode, print an error message and return RM_ERROR.
661 Otherwise, query the user if appropriate, then try to recursively
662 remove directory `pathname', which STATP contains info about.
663 Return 0 if `pathname' is removed, 1 if not.
664 FIXME: describe need_save_cwd parameter. */
666 static enum RM_status
667 remove_dir (struct File_spec *fs, int need_save_cwd)
669 enum RM_status status;
670 struct saved_cwd cwd;
671 char *dir_name = fs->filename;
672 const char *fmt = NULL;
676 error (0, 0, _("%s: is a directory"), full_filename (dir_name));
680 if (!ignore_missing_files && (interactive || stdin_tty)
681 && euidaccess (dir_name, W_OK))
683 fmt = _("directory `%s' is write protected; descend into it anyway? ");
685 else if (interactive)
687 fmt = _("descend into directory `%s'? ");
692 error (0, 0, fmt, full_filename (dir_name));
694 return RM_USER_DECLINED;
698 printf ("%s\n", full_filename (dir_name));
700 /* Save cwd if needed. */
701 if (need_save_cwd && save_cwd (&cwd))
704 /* Make target directory the current one. */
705 if (chdir (dir_name) < 0)
707 error (0, errno, _("cannot change to directory %s"),
708 full_filename (dir_name));
716 /* Save a copy of dir_name. Otherwise, remove_cwd_entries may clobber
717 dir_name because dir_name is just a pointer to the dir entry's d_name
718 field, and remove_cwd_entries may close the directory. */
719 ASSIGN_STRDUPA (dir_name, dir_name);
721 status = remove_cwd_entries ();
728 if (restore_cwd (&cwd, NULL, NULL))
735 else if (chdir ("..") < 0)
737 error (0, errno, _("cannot change back to directory %s via `..'"),
738 full_filename (dir_name));
744 error (0, 0, _("remove directory `%s'%s? "), full_filename (dir_name),
745 (status != RM_OK ? _(" (might be nonempty)") : ""));
748 return RM_USER_DECLINED;
752 if (rmdir (dir_name) && (errno != ENOENT || !ignore_missing_files))
754 error (0, errno, _("cannot remove directory `%s'"),
755 full_filename (dir_name));
762 /* Remove the file or directory specified by FS after checking appropriate
763 things. Return RM_OK if it is removed, and RM_ERROR or RM_USER_DECLINED
764 if not. If USER_SPECIFIED_NAME is non-zero, then the name part of FS may
765 be `.', `..', or may contain slashes. Otherwise, it must be a simple file
766 name (and hence must specify a file in the current directory). */
768 static enum RM_status
769 rm (struct File_spec *fs, int user_specified_name)
771 mode_t filetype_mode;
773 if (user_specified_name)
775 char *base = base_name (fs->filename);
777 if (DOT_OR_DOTDOT (base))
779 error (0, 0, _("cannot remove `.' or `..'"));
784 if (fspec_get_filetype_mode (fs, &filetype_mode))
786 if (ignore_missing_files && errno == ENOENT)
789 error (0, errno, _("cannot remove `%s'"), full_filename (fs->filename));
793 if (S_ISDIR (filetype_mode))
796 struct active_dir_ent *old_ent;
798 /* Insert this directory in the active_dir_map.
799 If there is already a directory in the map with the same inum,
800 then there's *probably* a directory cycle. This test can get
801 a false positive if two directories have the same inode number
802 but different device numbers and one directory contains the
803 other. But since people don't often try to delete hierarchies
804 containing mount points, and when they do, duplicate inode
805 numbers are not that likely, this isn't worth detecting. */
806 fail = hash_insert_item (active_dir_map,
807 make_active_dir_ent (fs->inum, current_depth ()),
810 error (1, 0, _("Memory exhausted"));
815 WARNING: Circular directory structure.\n\
816 This almost certainly means that you have a corrupted file system.\n\
817 NOTIFY YOUR SYSTEM MANAGER.\n\
818 The following two directories have the same inode number:\n"));
819 print_nth_dir (stderr, current_depth ());
820 fputc ('\n', stderr);
821 print_nth_dir (stderr, old_ent->depth);
822 fputc ('\n', stderr);
829 error (0, 0, _("continue? "));
837 if (!S_ISDIR (filetype_mode) || unlink_dirs)
839 return remove_file (fs);
843 int need_save_cwd = user_specified_name;
844 enum RM_status status;
845 struct active_dir_ent tmp;
846 struct active_dir_ent *old_ent;
849 need_save_cwd = (strchr (fs->filename, '/') != NULL);
851 status = remove_dir (fs, need_save_cwd);
853 /* Remove this directory from the active_dir_map. */
855 hash_delete_item (active_dir_map, &tmp, (void **) &old_ent);
856 assert (old_ent != NULL);
864 main (int argc, char **argv)
869 program_name = argv[0];
870 setlocale (LC_ALL, "");
871 bindtextdomain (PACKAGE, LOCALEDIR);
872 textdomain (PACKAGE);
874 verbose = ignore_missing_files = recursive = interactive
877 while ((c = getopt_long (argc, argv, "dfirvR", long_opts, NULL)) != -1)
881 case 0: /* Long option. */
888 ignore_missing_files = 1;
892 ignore_missing_files = 0;
908 printf ("rm (%s) %s\n", GNU_PACKAGE, VERSION);
917 if (ignore_missing_files)
921 error (0, 0, _("too few arguments"));
926 stdin_tty = isatty (STDIN_FILENO);
928 /* Initialize dir-stack obstacks. */
929 obstack_init (&dir_stack);
930 obstack_init (&len_stack);
932 active_dir_map = hash_init_table (NULL, ACTIVE_DIR_INITIAL_CAPACITY, 0, 0,
933 hash_active_dir_ent_1,
934 hash_active_dir_ent_2,
935 hash_compare_active_dir_ents);
937 for (; optind < argc; optind++)
940 enum RM_status status;
942 /* Stripping slashes is harmless for rmdir;
943 if the arg is not a directory, it will fail with ENOTDIR. */
944 strip_trailing_slashes (argv[optind]);
945 fspec_init_file (&fs, argv[optind]);
946 status = rm (&fs, 1);
947 assert (VALID_STATUS (status));
948 if (status == RM_ERROR)