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>
33 #define obstack_chunk_alloc malloc
34 #define obstack_chunk_free free
36 #ifdef D_INO_IN_DIRENT
37 # define D_INO(dp) ((dp)->d_ino)
39 /* Some systems don't have inodes, so fake them to avoid lots of ifdefs. */
43 #if !defined (S_ISLNK)
44 # define S_ISLNK(Mode) 0
47 #define DOT_OR_DOTDOT(Basename) \
48 (Basename[0] == '.' && (Basename[1] == '\0' \
49 || (Basename[1] == '.' && Basename[2] == '\0')))
52 # define ASSIGN_STRDUPA(DEST, S) \
53 do { DEST = strdupa(S); } while (0)
55 # define ASSIGN_STRDUPA(DEST, S) \
58 size_t len_ = strlen (S) + 1; \
59 char *tmp_dest_ = alloca (len_); \
60 memcpy (tmp_dest_, (S), len_); \
68 /* FIXME: describe and explain ordering: `ok' increasing in seriousness. */
74 #define VALID_STATUS(S) \
75 ((S) == RM_OK || (S) == RM_USER_DECLINED || (S) == RM_ERROR)
77 /* Initial capacity of per-directory hash table of entries that have
78 been processed but not been deleted. */
79 #define HT_INITIAL_CAPACITY 13
81 /* Initial capacity of the active directory hash table. This table will
82 be resized only for hierarchies more than about 45 levels deep. */
83 #define ACTIVE_DIR_INITIAL_CAPACITY 53
88 unsigned int have_filetype_mode:1;
89 unsigned int have_full_mode:1;
103 void strip_trailing_slashes ();
108 static enum RM_status rm (struct File_spec *fs, int user_specified_name);
110 /* Name this program was run with. */
113 /* If nonzero, display the name of each file removed. */
116 /* If nonzero, ignore nonexistant files. */
117 static int ignore_missing_files;
119 /* If nonzero, recursively remove directories. */
120 static int recursive;
122 /* If nonzero, query the user about whether to remove each file. */
123 static int interactive;
125 /* If nonzero, remove directories with unlink instead of rmdir, and don't
126 require a directory to be empty before trying to unlink it.
127 Only works for the super-user. */
128 static int unlink_dirs;
130 /* If nonzero, stdin is a tty. */
131 static int stdin_tty;
133 /* If nonzero, display usage information and exit. */
134 static int show_help;
136 /* If nonzero, print the version on standard output and exit. */
137 static int show_version;
139 /* FIXME: describe */
140 static struct obstack dir_stack;
142 /* Stack of lengths of directory names (including trailing slash)
143 appended to dir_stack. We have to have a separate stack of lengths
144 (rather than just popping back to previous slash) because the first
145 element pushed onto the dir stack may contain slashes. */
146 static struct obstack len_stack;
148 /* Set of `active' directories from the current command-line parameter
149 to the level in the hierarchy at which files are being removed.
150 A directory is added to the active set when RM begins removing it
151 (or its entries), and it is removed from the set just after RM has
152 finished processing it.
154 This is actually a map (not a set), implemented with a hash table.
155 For each active directory, it maps the directory's inode number to the
156 depth of that directory relative to the root of the tree being deleted.
157 A directory specified on the command line has depth zero.
158 This construct is used to detect directory cycles so that RM can warn
159 about them rather than iterating endlessly. */
160 static struct hash_table *active_dir_map;
162 /* An entry in the active_dir_map. */
163 struct active_dir_ent
169 static struct option const long_opts[] =
171 {"directory", no_argument, &unlink_dirs, 1},
172 {"force", no_argument, NULL, 'f'},
173 {"interactive", no_argument, NULL, 'i'},
174 {"recursive", no_argument, &recursive, 1},
175 {"verbose", no_argument, &verbose, 1},
176 {"help", no_argument, &show_help, 1},
177 {"version", no_argument, &show_version, 1},
181 static __inline unsigned int
184 return obstack_object_size (&len_stack) / sizeof (size_t);
188 print_nth_dir (FILE *stream, unsigned int depth)
190 size_t *length = (size_t *) obstack_base (&len_stack);
191 char *dir_name = (char *) obstack_base (&dir_stack);
192 unsigned int sum = 0;
195 assert (0 <= depth && depth < current_depth ());
197 for (i = 0; i <= depth; i++)
202 fwrite (dir_name, 1, sum, stream);
205 static __inline struct active_dir_ent *
206 make_active_dir_ent (ino_t inum, unsigned int depth)
208 struct active_dir_ent *ent;
209 ent = (struct active_dir_ent *) xmalloc (sizeof *ent);
216 hash_active_dir_ent_1 (void const *x)
218 struct active_dir_ent const *ade = x;
219 return_INTEGER_HASH_1 (ade->inum);
223 hash_active_dir_ent_2 (void const *x)
225 struct active_dir_ent const *ade = x;
226 return_INTEGER_HASH_2 (ade->inum);
230 hash_compare_active_dir_ents (void const *x, void const *y)
232 struct active_dir_ent const *a = x;
233 struct active_dir_ent const *b = y;
234 return_INTEGER_COMPARE (a->inum, b->inum);
238 hash_string_1 (void const *x)
240 return_STRING_HASH_1 (x);
244 hash_string_2 (void const *x)
246 return_STRING_HASH_2 (x);
250 hash_compare_strings (void const *x, void const *y)
252 return strcmp (x, y);
259 fprintf (stderr, _("Try `%s --help' for more information.\n"),
263 printf (_("Usage: %s [OPTION]... FILE...\n"), program_name);
265 Remove (unlink) the FILE(s).\n\
267 -d, --directory unlink directory, even if non-empty (super-user only)\n\
268 -f, --force ignore nonexistent files, never prompt\n\
269 -i, --interactive prompt before any removal\n\
270 -r, -R, --recursive remove the contents of directories recursively\n\
271 -v, --verbose explain what is being done\n\
272 --help display this help and exit\n\
273 --version output version information and exit\n\
275 puts (_("\nReport bugs to <fileutils-bugs@gnu.ai.mit.edu>."));
281 push_dir (const char *dir_name)
285 len = strlen (dir_name);
287 /* Append the string onto the stack. */
288 obstack_grow (&dir_stack, dir_name, len);
290 /* Append a trailing slash. */
291 obstack_1grow (&dir_stack, '/');
293 /* Add one for the slash. */
296 /* Push the length (including slash) onto its stack. */
297 obstack_grow (&len_stack, &len, sizeof (len));
303 int n_lengths = obstack_object_size (&len_stack) / sizeof (size_t);
304 size_t *length = (size_t *) obstack_base (&len_stack);
307 assert (n_lengths > 0);
308 top_len = length[n_lengths - 1];
309 assert (top_len >= 2);
311 /* Pop off the specified length of pathname. */
312 assert (obstack_object_size (&dir_stack) >= top_len);
313 obstack_blank (&dir_stack, -top_len);
315 /* Pop the length stack, too. */
316 assert (obstack_object_size (&len_stack) >= sizeof (size_t));
317 obstack_blank (&len_stack, -(sizeof (size_t)));
320 /* Copy the SRC_LEN bytes of data beginning at SRC into the DST_LEN-byte
321 buffer, DST, so that the last source byte is at the end of the destination
322 buffer. If SRC_LEN is longer than DST_LEN, then set *TRUNCATED to non-zero.
323 Set *RESULT to point to the beginning of (the portion of) the source data
324 in DST. Return the number of bytes remaining in the destination buffer. */
327 right_justify (char *dst, size_t dst_len, const char *src, size_t src_len,
328 char **result, int *truncated)
333 if (src_len <= dst_len)
336 dp = dst + (dst_len - src_len);
341 sp = src + (src_len - dst_len);
347 memcpy (dp, sp, src_len);
350 return dst_len - src_len;
353 /* Using the global directory name obstack, create the full path to FILENAME.
354 Return it in sometimes-realloc'd space that should not be freed by the
355 caller. Realloc as necessary. If realloc fails, use a static buffer
356 and put as long a suffix in that buffer as possible. */
359 full_filename (const char *filename)
361 static char *buf = NULL;
362 static size_t n_allocated = 0;
364 int dir_len = obstack_object_size (&dir_stack);
365 char *dir_name = (char *) obstack_base (&dir_stack);
366 size_t n_bytes_needed;
369 filename_len = strlen (filename);
370 n_bytes_needed = dir_len + filename_len + 1;
372 if (n_bytes_needed > n_allocated)
374 /* FIXME: use realloc, not xrealloc. */
375 /* But be sure realloc accepts NULL first arg.
376 FIXME: replace with rpl_realloc if not. */
377 /* This funciton can't use xrealloc. Otherwise, out-of-memory
378 errors involving a file name to be expanded here wouldn't ever
379 be issued. Use realloc and fall back on using a static buffer
380 if memory is a problem. */
381 buf = xrealloc (buf, n_bytes_needed);
382 n_allocated = n_bytes_needed;
386 #define SBUF_SIZE 512
387 #define ELLIPSES_PREFIX "[...]"
388 static char static_buf[SBUF_SIZE];
393 len = right_justify (static_buf, SBUF_SIZE, filename,
394 filename_len + 1, &p, &truncated);
395 right_justify (static_buf, len, dir_name, dir_len, &p, &truncated);
398 memcpy (static_buf, ELLIPSES_PREFIX,
399 sizeof (ELLIPSES_PREFIX) - 1);
405 /* Copy directory part, including trailing slash. */
406 memcpy (buf, dir_name, dir_len);
408 /* Append filename part, including trailing zero byte. */
409 memcpy (buf + dir_len, filename, filename_len + 1);
411 assert (strlen (buf) + 1 == n_bytes_needed);
417 fspec_init_file (struct File_spec *fs, const char *filename)
419 fs->filename = (char *) filename;
420 fs->have_full_mode = 0;
421 fs->have_filetype_mode = 0;
425 fspec_init_dp (struct File_spec *fs, struct dirent *dp)
427 fs->filename = dp->d_name;
428 fs->have_full_mode = 0;
429 fs->have_filetype_mode = 0;
430 fs->inum = D_INO (dp);
432 #if D_TYPE_IN_DIRENT && defined (DT_UNKNOWN)
433 if (filetype_mode != DT_UNKNOWN)
435 fs->have_filetype_mode = 1;
436 fs->mode = filetype_mode;
442 fspec_get_full_mode (struct File_spec *fs, mode_t *full_mode)
444 struct stat stat_buf;
446 if (fs->have_full_mode)
448 *full_mode = fs->mode;
452 if (lstat (fs->filename, &stat_buf))
455 fs->have_full_mode = 1;
456 fs->have_filetype_mode = 1;
457 fs->mode = stat_buf.st_mode;
458 fs->inum = stat_buf.st_ino;
460 *full_mode = stat_buf.st_mode;
465 fspec_get_filetype_mode (struct File_spec *fs, mode_t *filetype_mode)
469 if (fs->have_filetype_mode)
471 *filetype_mode = fs->mode;
476 fail = fspec_get_full_mode (fs, filetype_mode);
482 static __inline mode_t
483 fspec_filetype_mode (const struct File_spec *fs)
485 assert (fs->have_filetype_mode);
489 /* Recursively remove all of the entries in the current directory.
490 Return an indication of the success of the operation. */
493 remove_cwd_entries (void)
495 /* NOTE: this is static. */
496 static DIR *dirp = NULL;
498 /* NULL or a malloc'd and initialized hash table of entries in the
499 current directory that have been processed but not removed --
500 due either to an error or to an interactive `no' response. */
501 struct hash_table *ht = NULL;
504 enum RM_status status = RM_OK;
510 /* FIXME-someday: but this is actually the previously opened dir. */
511 error (0, errno, "%s", full_filename ("."));
519 dirp = opendir (".");
522 if (errno != ENOENT || !ignore_missing_files)
524 error (0, errno, "%s", full_filename ("."));
530 while ((dp = readdir (dirp)) != NULL)
532 /* Skip this entry if it's `.' or `..'. */
533 if (DOT_OR_DOTDOT (dp->d_name))
536 /* Skip this entry if it's in the table of ones we've already
538 if (ht && hash_find_item (ht, (dp)->d_name))
543 enum RM_status tmp_status;
544 fspec_init_dp (&fs, dp);
545 tmp_status = rm (&fs, 0);
547 if (tmp_status > status)
549 assert (VALID_STATUS (status));
552 /* If this entry was not removed (due either to an error or to
553 an interactive `no' response), record it in the hash table so
554 we don't consider it again if we reopen this directory later. */
563 ht = hash_init_table (NULL, HT_INITIAL_CAPACITY, 0, 0,
564 hash_string_1, hash_string_2,
565 hash_compare_strings);
567 error (1, 0, _("Memory exhausted"));
569 p = xmalloc (NLENGTH (dp) + 1);
570 stpncpy (p, (dp)->d_name, NLENGTH (dp));
571 fail = hash_insert_item (ht, p, &old_item);
572 assert (old_item == NULL);
574 error (1, 0, _("Memory exhausted"));
581 while (dirp == NULL);
585 error (0, errno, "%s", full_filename ("."));
592 hash_free_items (ht);
593 hash_free_table (ht);
600 /* Query the user if appropriate, and if ok try to remove the
601 file or directory specified by FS. Return RM_OK if it is removed,
602 and RM_ERROR or RM_USER_DECLINED if not.
603 FIXME: describe IS_DIR parameter. */
605 static enum RM_status
606 remove_file (struct File_spec *fs)
609 char *pathname = fs->filename;
611 if (!ignore_missing_files && (interactive || stdin_tty)
612 && euidaccess (pathname, W_OK) )
614 if (!S_ISLNK (fspec_filetype_mode (fs)))
617 (S_ISDIR (fspec_filetype_mode (fs))
618 ? _("remove write-protected directory `%s'? ")
619 : _("remove write-protected file `%s'? ")),
620 full_filename (pathname));
622 return RM_USER_DECLINED;
628 if (!asked && interactive)
631 (S_ISDIR (fspec_filetype_mode (fs))
632 ? _("remove directory `%s'? ")
633 : _("remove `%s'? ")),
634 full_filename (pathname));
636 return RM_USER_DECLINED;
640 printf ("%s\n", full_filename (pathname));
642 if (unlink (pathname) && (errno != ENOENT || !ignore_missing_files))
644 error (0, errno, _("cannot unlink `%s'"), full_filename (pathname));
650 /* If not in recursive mode, print an error message and return RM_ERROR.
651 Otherwise, query the user if appropriate, then try to recursively
652 remove directory `pathname', which STATP contains info about.
653 Return 0 if `pathname' is removed, 1 if not.
654 FIXME: describe need_save_cwd parameter. */
656 static enum RM_status
657 remove_dir (struct File_spec *fs, int need_save_cwd)
659 enum RM_status status;
660 struct saved_cwd cwd;
661 char *dir_name = fs->filename;
662 const char *fmt = NULL;
666 error (0, 0, _("%s: is a directory"), full_filename (dir_name));
670 if (!ignore_missing_files && (interactive || stdin_tty)
671 && euidaccess (dir_name, W_OK))
673 fmt = _("directory `%s' is write protected; descend into it anyway? ");
675 else if (interactive)
677 fmt = _("descend into directory `%s'? ");
682 error (0, 0, fmt, full_filename (dir_name));
684 return RM_USER_DECLINED;
688 printf ("%s\n", full_filename (dir_name));
690 /* Save cwd if needed. */
691 if (need_save_cwd && save_cwd (&cwd))
694 /* Make target directory the current one. */
695 if (chdir (dir_name) < 0)
697 error (0, errno, _("cannot change to directory %s"),
698 full_filename (dir_name));
706 /* Save a copy of dir_name. Otherwise, remove_cwd_entries may clobber
707 dir_name because dir_name is just a pointer to the dir entry's d_name
708 field, and remove_cwd_entries may close the directory. */
709 ASSIGN_STRDUPA (dir_name, dir_name);
711 status = remove_cwd_entries ();
718 if (restore_cwd (&cwd, NULL, NULL))
725 else if (chdir ("..") < 0)
727 error (0, errno, _("cannot change back to directory %s via `..'"),
728 full_filename (dir_name));
734 error (0, 0, _("remove directory `%s'%s? "), full_filename (dir_name),
735 (status != RM_OK ? _(" (might be nonempty)") : ""));
738 return RM_USER_DECLINED;
742 if (rmdir (dir_name) && (errno != ENOENT || !ignore_missing_files))
744 error (0, errno, _("cannot remove directory `%s'"),
745 full_filename (dir_name));
752 /* Remove the file or directory specified by FS after checking appropriate
753 things. Return RM_OK if it is removed, and RM_ERROR or RM_USER_DECLINED
754 if not. If USER_SPECIFIED_NAME is non-zero, then the name part of FS may
755 be `.', `..', or may contain slashes. Otherwise, it must be a simple file
756 name (and hence must specify a file in the current directory). */
758 static enum RM_status
759 rm (struct File_spec *fs, int user_specified_name)
761 mode_t filetype_mode;
763 if (user_specified_name)
765 char *base = base_name (fs->filename);
767 if (DOT_OR_DOTDOT (base))
769 error (0, 0, _("cannot remove `.' or `..'"));
774 if (fspec_get_filetype_mode (fs, &filetype_mode))
776 if (ignore_missing_files && errno == ENOENT)
779 error (0, errno, _("cannot remove `%s'"), full_filename (fs->filename));
783 if (S_ISDIR (filetype_mode))
786 struct active_dir_ent *old_ent;
788 /* Insert this directory in the active_dir_map.
789 If there is already a directory in the map with the same inum,
790 then there's *probably* a directory cycle. This test can get
791 a false positive if two directories have the same inode number
792 but different device numbers and one directory contains the
793 other. But since people don't often try to delete hierarchies
794 containing mount points, and when they do, duplicate inode
795 numbers are not that likely, this isn't worth detecting. */
796 fail = hash_insert_item (active_dir_map,
797 make_active_dir_ent (fs->inum, current_depth ()),
800 error (1, 0, _("Memory exhausted"));
805 WARNING: Circular directory structure.\n\
806 This almost certainly means that you have a corrupted file system.\n\
807 NOTIFY YOUR SYSTEM MANAGER.\n\
808 The following two directories have the same inode number:\n"));
809 print_nth_dir (stderr, current_depth ());
810 fputc ('\n', stderr);
811 print_nth_dir (stderr, old_ent->depth);
812 fputc ('\n', stderr);
819 error (0, 0, _("continue? "));
827 if (!S_ISDIR (filetype_mode) || unlink_dirs)
829 return remove_file (fs);
833 int need_save_cwd = user_specified_name;
834 enum RM_status status;
835 struct active_dir_ent tmp;
836 struct active_dir_ent *old_ent;
839 need_save_cwd = (strchr (fs->filename, '/') != NULL);
841 status = remove_dir (fs, need_save_cwd);
843 /* Remove this directory from the active_dir_map. */
845 hash_delete_item (active_dir_map, &tmp, (void **) &old_ent);
846 assert (old_ent != NULL);
854 main (int argc, char **argv)
859 program_name = argv[0];
860 setlocale (LC_ALL, "");
861 bindtextdomain (PACKAGE, LOCALEDIR);
862 textdomain (PACKAGE);
864 verbose = ignore_missing_files = recursive = interactive
867 while ((c = getopt_long (argc, argv, "dfirvR", long_opts, NULL)) != -1)
871 case 0: /* Long option. */
878 ignore_missing_files = 1;
882 ignore_missing_files = 0;
898 printf ("rm (%s) %s\n", GNU_PACKAGE, VERSION);
907 if (ignore_missing_files)
911 error (0, 0, _("too few arguments"));
916 stdin_tty = isatty (STDIN_FILENO);
918 /* Initialize dir-stack obstacks. */
919 obstack_init (&dir_stack);
920 obstack_init (&len_stack);
922 active_dir_map = hash_init_table (NULL, ACTIVE_DIR_INITIAL_CAPACITY, 0, 0,
923 hash_active_dir_ent_1,
924 hash_active_dir_ent_2,
925 hash_compare_active_dir_ents);
927 for (; optind < argc; optind++)
930 enum RM_status status;
932 /* Stripping slashes is harmless for rmdir;
933 if the arg is not a directory, it will fail with ENOTDIR. */
934 strip_trailing_slashes (argv[optind]);
935 fspec_init_file (&fs, argv[optind]);
936 status = rm (&fs, 1);
937 assert (VALID_STATUS (status));
938 if (status == RM_ERROR)