*** empty log message ***
[coreutils.git] / src / remove.c
blob2e31323ab9f1d2e0b3812aa33e072399ae28ebb3
1 /* remove.c -- core functions for removing files and directories
2 Copyright (C) 88, 90, 91, 1994-2001 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)
7 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, write to the Free Software Foundation,
16 Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */
18 /* Extracted from rm.c and librarified by Jim Meyering. */
20 #ifdef _AIX
21 #pragma alloca
22 #endif
24 #include <config.h>
25 #include <stdio.h>
26 #include <sys/types.h>
27 #include <assert.h>
29 #if HAVE_STDBOOL_H
30 # include <stdbool.h>
31 #else
32 typedef enum {false = 0, true = 1} bool;
33 #endif
35 #include "save-cwd.h"
36 #include "system.h"
37 #include "error.h"
38 #include "obstack.h"
39 #include "hash.h"
40 #include "quote.h"
41 #include "remove.h"
43 #define obstack_chunk_alloc malloc
44 #define obstack_chunk_free free
46 #ifndef PARAMS
47 # if defined (__GNUC__) || __STDC__
48 # define PARAMS(args) args
49 # else
50 # define PARAMS(args) ()
51 # endif
52 #endif
54 /* On systems with an lstat function that accepts the empty string,
55 arrange to make lstat calls go through the wrapper function. */
56 #if HAVE_LSTAT_EMPTY_STRING_BUG
57 int rpl_lstat PARAMS((const char *, struct stat *));
58 # define lstat(Name, Stat_buf) rpl_lstat(Name, Stat_buf)
59 #endif
61 #ifdef D_INO_IN_DIRENT
62 # define D_INO(dp) ((dp)->d_ino)
63 # define ENABLE_CYCLE_CHECK
64 #else
65 /* Some systems don't have inodes, so fake them to avoid lots of ifdefs. */
66 # define D_INO(dp) 1
67 #endif
69 #if !defined S_ISLNK
70 # define S_ISLNK(Mode) 0
71 #endif
73 /* Initial capacity of per-directory hash table of entries that have
74 been processed but not been deleted. */
75 #define HT_INITIAL_CAPACITY 13
77 /* Initial capacity of the active directory hash table. This table will
78 be resized only for hierarchies more than about 45 levels deep. */
79 #define ACTIVE_DIR_INITIAL_CAPACITY 53
81 int euidaccess ();
82 int yesno ();
84 extern char *program_name;
86 /* state initialized by remove_init, freed by remove_fini */
88 /* An entry in the active_dir_map. */
89 struct active_dir_ent
91 ino_t st_ino;
92 dev_t st_dev;
93 unsigned int depth;
96 /* The name of the directory (starting with and relative to a command
97 line argument) being processed. When a subdirectory is entered, a new
98 component is appended (pushed). When RM chdir's out of a directory,
99 the top component is removed (popped). This is used to form a full
100 file name when necessary. */
101 static struct obstack dir_stack;
103 /* Stack of lengths of directory names (including trailing slash)
104 appended to dir_stack. We have to have a separate stack of lengths
105 (rather than just popping back to previous slash) because the first
106 element pushed onto the dir stack may contain slashes. */
107 static struct obstack len_stack;
109 /* Set of `active' directories from the current command-line argument
110 to the level in the hierarchy at which files are being removed.
111 A directory is added to the active set when RM begins removing it
112 (or its entries), and it is removed from the set just after RM has
113 finished processing it.
115 This is actually a map (not a set), implemented with a hash table.
116 For each active directory, it maps the directory's inode number to the
117 depth of that directory relative to the root of the tree being deleted.
118 A directory specified on the command line has depth zero.
119 This construct is used to detect directory cycles so that RM can warn
120 about them rather than iterating endlessly. */
121 #ifdef ENABLE_CYCLE_CHECK
122 static struct hash_table *active_dir_map;
123 #endif
125 static inline unsigned int
126 current_depth (void)
128 return obstack_object_size (&len_stack) / sizeof (size_t);
131 static void
132 print_nth_dir (FILE *stream, unsigned int depth)
134 size_t *length = (size_t *) obstack_base (&len_stack);
135 char *dir_name = (char *) obstack_base (&dir_stack);
136 unsigned int sum = 0;
137 unsigned int i;
139 assert (depth < current_depth ());
141 for (i = 0; i <= depth; i++)
143 sum += length[i];
146 fwrite (dir_name, 1, sum - 1, stream);
149 static inline struct active_dir_ent *
150 make_active_dir_ent (ino_t inum, dev_t device, unsigned int depth)
152 struct active_dir_ent *ent;
153 ent = (struct active_dir_ent *) xmalloc (sizeof *ent);
154 ent->st_ino = inum;
155 ent->st_dev = device;
156 ent->depth = depth;
157 return ent;
160 static unsigned int
161 hash_active_dir_ent (void const *x, unsigned int table_size)
163 struct active_dir_ent const *ade = x;
165 /* Ignoring the device number here should be fine. */
166 return ade->st_ino % table_size;
169 static bool
170 hash_compare_active_dir_ents (void const *x, void const *y)
172 struct active_dir_ent const *a = x;
173 struct active_dir_ent const *b = y;
174 return SAME_INODE (*a, *b) ? true : false;
177 /* A hash function for null-terminated char* strings using
178 the method described in Aho, Sethi, & Ullman, p 436. */
180 static unsigned int
181 hash_pjw (const void *x, unsigned int tablesize)
183 const char *s = x;
184 unsigned int h = 0;
185 unsigned int g;
187 while (*s != 0)
189 h = (h << 4) + *s++;
190 if ((g = h & (unsigned int) 0xf0000000) != 0)
191 h = (h ^ (g >> 24)) ^ g;
194 return (h % tablesize);
197 static bool
198 hash_compare_strings (void const *x, void const *y)
200 return STREQ (x, y) ? true : false;
203 static inline void
204 push_dir (const char *dir_name)
206 size_t len;
208 len = strlen (dir_name);
210 /* Append the string onto the stack. */
211 obstack_grow (&dir_stack, dir_name, len);
213 /* Append a trailing slash. */
214 obstack_1grow (&dir_stack, '/');
216 /* Add one for the slash. */
217 ++len;
219 /* Push the length (including slash) onto its stack. */
220 obstack_grow (&len_stack, &len, sizeof (len));
223 static inline void
224 pop_dir (void)
226 int n_lengths = obstack_object_size (&len_stack) / sizeof (size_t);
227 size_t *length = (size_t *) obstack_base (&len_stack);
228 size_t top_len;
230 assert (n_lengths > 0);
231 top_len = length[n_lengths - 1];
232 assert (top_len >= 2);
234 /* Pop off the specified length of pathname. */
235 assert (obstack_object_size (&dir_stack) >= top_len);
236 obstack_blank (&dir_stack, -top_len);
238 /* Pop the length stack, too. */
239 assert (obstack_object_size (&len_stack) >= sizeof (size_t));
240 obstack_blank (&len_stack, (int) -(sizeof (size_t)));
243 /* Copy the SRC_LEN bytes of data beginning at SRC into the DST_LEN-byte
244 buffer, DST, so that the last source byte is at the end of the destination
245 buffer. If SRC_LEN is longer than DST_LEN, then set *TRUNCATED to non-zero.
246 Set *RESULT to point to the beginning of (the portion of) the source data
247 in DST. Return the number of bytes remaining in the destination buffer. */
249 static size_t
250 right_justify (char *dst, size_t dst_len, const char *src, size_t src_len,
251 char **result, int *truncated)
253 const char *sp;
254 char *dp;
256 if (src_len <= dst_len)
258 sp = src;
259 dp = dst + (dst_len - src_len);
260 *truncated = 0;
262 else
264 sp = src + (src_len - dst_len);
265 dp = dst;
266 src_len = dst_len;
267 *truncated = 1;
270 *result = memcpy (dp, sp, src_len);
271 return dst_len - src_len;
274 /* Using the global directory name obstack, create the full path to FILENAME.
275 Return it in sometimes-realloc'd space that should not be freed by the
276 caller. Realloc as necessary. If realloc fails, use a static buffer
277 and put as long a suffix in that buffer as possible. */
279 static char *
280 full_filename (const char *filename)
282 static char *buf = NULL;
283 static size_t n_allocated = 0;
285 int dir_len = obstack_object_size (&dir_stack);
286 char *dir_name = (char *) obstack_base (&dir_stack);
287 size_t n_bytes_needed;
288 size_t filename_len;
290 filename_len = strlen (filename);
291 n_bytes_needed = dir_len + filename_len + 1;
293 if (n_bytes_needed > n_allocated)
295 /* This code requires that realloc accept NULL as the first arg.
296 This function must not use xrealloc. Otherwise, an out-of-memory
297 error involving a file name to be expanded here wouldn't ever
298 be issued. Use realloc and fall back on using a static buffer
299 if memory allocation fails. */
300 buf = realloc (buf, n_bytes_needed);
301 n_allocated = n_bytes_needed;
303 if (buf == NULL)
305 #define SBUF_SIZE 512
306 #define ELLIPSES_PREFIX "[...]"
307 static char static_buf[SBUF_SIZE];
308 int truncated;
309 size_t len;
310 char *p;
312 len = right_justify (static_buf, SBUF_SIZE, filename,
313 filename_len + 1, &p, &truncated);
314 right_justify (static_buf, len, dir_name, dir_len, &p, &truncated);
315 if (truncated)
317 memcpy (static_buf, ELLIPSES_PREFIX,
318 sizeof (ELLIPSES_PREFIX) - 1);
320 return p;
324 /* Copy directory part, including trailing slash, and then
325 append the filename part, including a trailing zero byte. */
326 memcpy (mempcpy (buf, dir_name, dir_len), filename, filename_len + 1);
328 assert (strlen (buf) + 1 == n_bytes_needed);
330 return buf;
333 static inline void
334 fspec_init_common (struct File_spec *fs)
336 fs->have_full_mode = 0;
337 fs->have_filetype_mode = 0;
338 fs->have_device = 0;
341 void
342 fspec_init_file (struct File_spec *fs, const char *filename)
344 fs->filename = (char *) filename;
345 fspec_init_common (fs);
348 static inline void
349 fspec_init_dp (struct File_spec *fs, struct dirent *dp)
351 fs->filename = dp->d_name;
352 fspec_init_common (fs);
353 fs->st_ino = D_INO (dp);
355 #if D_TYPE_IN_DIRENT && defined DT_UNKNOWN && defined DTTOIF
356 if (dp->d_type != DT_UNKNOWN)
358 fs->have_filetype_mode = 1;
359 fs->mode = DTTOIF (dp->d_type);
361 #endif
364 static inline int
365 fspec_get_full_mode (struct File_spec *fs)
367 struct stat stat_buf;
369 if (fs->have_full_mode)
370 return 0;
372 if (lstat (fs->filename, &stat_buf))
373 return 1;
375 fs->have_full_mode = 1;
376 fs->have_filetype_mode = 1;
377 fs->mode = stat_buf.st_mode;
378 fs->st_ino = stat_buf.st_ino;
379 fs->have_device = 1;
380 fs->st_dev = stat_buf.st_dev;
382 return 0;
385 static inline int
386 fspec_get_device_number (struct File_spec *fs)
388 struct stat stat_buf;
390 if (fs->have_device)
391 return 0;
393 if (lstat (fs->filename, &stat_buf))
394 return 1;
396 fs->have_full_mode = 1;
397 fs->have_filetype_mode = 1;
398 fs->mode = stat_buf.st_mode;
399 fs->st_ino = stat_buf.st_ino;
400 fs->have_device = 1;
401 fs->st_dev = stat_buf.st_dev;
403 return 0;
406 static inline int
407 fspec_get_filetype_mode (struct File_spec *fs, mode_t *filetype_mode)
409 int fail;
411 fail = fs->have_filetype_mode ? 0 : fspec_get_full_mode (fs);
412 if (!fail)
413 *filetype_mode = fs->mode;
415 return fail;
418 static inline mode_t
419 fspec_filetype_mode (const struct File_spec *fs)
421 assert (fs->have_filetype_mode);
422 return fs->mode;
425 static int
426 same_file (const char *file_1, const char *file_2)
428 struct stat sb1, sb2;
429 return (lstat (file_1, &sb1) == 0
430 && lstat (file_2, &sb2) == 0
431 && SAME_INODE (sb1, sb2));
435 /* Recursively remove all of the entries in the current directory.
436 Return an indication of the success of the operation. */
438 static enum RM_status
439 remove_cwd_entries (const struct rm_options *x)
441 /* NOTE: this is static. */
442 static DIR *dirp = NULL;
444 /* NULL or a malloc'd and initialized hash table of entries in the
445 current directory that have been processed but not removed --
446 due either to an error or to an interactive `no' response. */
447 struct hash_table *ht = NULL;
449 /* FIXME: describe */
450 static struct obstack entry_name_pool;
451 static int first_call = 1;
453 enum RM_status status = RM_OK;
455 if (first_call)
457 first_call = 0;
458 obstack_init (&entry_name_pool);
461 if (dirp)
463 if (CLOSEDIR (dirp))
465 /* FIXME-someday: but this is actually the previously opened dir. */
466 error (0, errno, "%s", quote (full_filename (".")));
467 status = RM_ERROR;
469 dirp = NULL;
474 /* FIXME: why do this? */
475 errno = 0;
477 dirp = opendir (".");
478 if (dirp == NULL)
480 if (errno != ENOENT || !x->ignore_missing_files)
482 error (0, errno, _("cannot open directory %s"),
483 quote (full_filename (".")));
484 status = RM_ERROR;
486 break;
489 while (1)
491 char *entry_name;
492 struct File_spec fs;
493 enum RM_status tmp_status;
494 struct dirent *dp;
496 /* FILE should be skipped if it is `.' or `..', or if it is in
497 the table, HT, of entries we've already processed. */
498 #define SKIPPABLE(Ht, File) \
499 (DOT_OR_DOTDOT(File) || (Ht && hash_lookup (Ht, File)))
501 /* FIXME: use readdir_r directly into an obstack to avoid
502 the obstack_copy0 below --
503 Suggestion from Uli. Be careful -- there are different
504 prototypes on e.g. Solaris.
506 Do something like this:
507 #define NAME_MAX_FOR(Parent_dir) pathconf ((Parent_dir),
508 _PC_NAME_MAX);
509 dp = obstack_alloc (sizeof (struct dirent)
510 + NAME_MAX_FOR (".") + 1);
511 fail = xreaddir (dirp, dp);
512 where xreaddir is ...
514 But what about systems like the hurd where NAME_MAX is supposed
515 to be effectively unlimited. We don't want to have to allocate
516 a huge buffer to accommodate maximum possible entry name. */
518 dp = readdir (dirp);
520 #if ! HAVE_WORKING_READDIR
521 if (dp == NULL)
523 /* Since we have probably modified the directory since it
524 was opened, readdir returning NULL does not necessarily
525 mean we have read the last entry. Rewind it and check
526 again. This happens on SunOS4.1.4 with 254 or more files
527 in a directory. */
528 rewinddir (dirp);
529 while ((dp = readdir (dirp)) && SKIPPABLE (ht, dp->d_name))
531 /* empty */
534 #endif
536 if (dp == NULL)
537 break;
539 if (SKIPPABLE (ht, dp->d_name))
540 continue;
542 fspec_init_dp (&fs, dp);
544 /* Save a copy of the name of this entry, in case we have
545 to add it to the set of unremoved entries below. */
546 entry_name = obstack_copy0 (&entry_name_pool,
547 dp->d_name, NLENGTH (dp));
549 /* CAUTION: after this call to rm, DP may not be valid --
550 it may have been freed due to a close in a recursive call
551 (through rm and remove_dir) to this function. */
552 tmp_status = rm (&fs, 0, x);
554 /* Update status. */
555 if (tmp_status > status)
556 status = tmp_status;
557 assert (VALID_STATUS (status));
559 /* If this entry was not removed (due either to an error or to
560 an interactive `no' response), record it in the hash table so
561 we don't consider it again if we reopen this directory later. */
562 if (status != RM_OK)
564 if (ht == NULL)
566 ht = hash_initialize (HT_INITIAL_CAPACITY, NULL, hash_pjw,
567 hash_compare_strings, NULL);
568 if (ht == NULL)
569 xalloc_die ();
571 if (! hash_insert (ht, entry_name))
572 xalloc_die ();
574 else
576 /* This entry was not saved in the hash table. Free it. */
577 obstack_free (&entry_name_pool, entry_name);
580 if (dirp == NULL)
581 break;
584 while (dirp == NULL);
586 if (dirp)
588 if (CLOSEDIR (dirp))
590 error (0, errno, _("closing directory %s"),
591 quote (full_filename (".")));
592 status = RM_ERROR;
594 dirp = NULL;
597 if (ht)
599 hash_free (ht);
602 if (obstack_object_size (&entry_name_pool) > 0)
603 obstack_free (&entry_name_pool, obstack_base (&entry_name_pool));
605 return status;
608 /* Query the user if appropriate, and if ok try to remove the
609 file or directory specified by FS. Return RM_OK if it is removed,
610 and RM_ERROR or RM_USER_DECLINED if not. */
612 static enum RM_status
613 remove_file (struct File_spec *fs, const struct rm_options *x)
615 int asked = 0;
616 char *pathname = fs->filename;
618 if (!x->ignore_missing_files && x->interactive && x->stdin_tty
619 && euidaccess (pathname, W_OK))
621 if (!S_ISLNK (fspec_filetype_mode (fs)))
623 fprintf (stderr,
624 (S_ISDIR (fspec_filetype_mode (fs))
625 ? _("%s: remove write-protected directory %s? ")
626 : _("%s: remove write-protected file %s? ")),
627 program_name, quote (full_filename (pathname)));
628 if (!yesno ())
629 return RM_USER_DECLINED;
631 asked = 1;
635 if (!asked && x->interactive)
637 /* FIXME: use a variant of error (instead of fprintf) that doesn't
638 append a newline. Then we won't have to declare program_name in
639 this file. */
640 fprintf (stderr,
641 (S_ISDIR (fspec_filetype_mode (fs))
642 ? _("%s: remove directory %s? ")
643 : _("%s: remove %s? ")),
644 program_name, quote (full_filename (pathname)));
645 if (!yesno ())
646 return RM_USER_DECLINED;
649 if (x->verbose)
650 printf (_("removing %s\n"), quote (full_filename (pathname)));
652 if (unlink (pathname) && (errno != ENOENT || !x->ignore_missing_files))
654 error (0, errno, _("cannot unlink %s"), quote (full_filename (pathname)));
655 return RM_ERROR;
657 return RM_OK;
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 the directory specified by FS. Return RM_OK if it is removed,
663 and RM_ERROR or RM_USER_DECLINED 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, const struct rm_options *x)
669 enum RM_status status;
670 struct saved_cwd cwd;
671 char *dir_name = fs->filename;
672 const char *fmt = NULL;
674 if (!x->recursive)
676 error (0, 0, _("%s is a directory"), quote (full_filename (dir_name)));
677 return RM_ERROR;
680 if (!x->ignore_missing_files && (x->interactive || x->stdin_tty)
681 && euidaccess (dir_name, W_OK))
683 fmt = _("%s: directory %s is write protected; descend into it anyway? ");
685 else if (x->interactive)
687 fmt = _("%s: descend into directory %s? ");
690 if (fmt)
692 fprintf (stderr, fmt, program_name, quote (full_filename (dir_name)));
693 if (!yesno ())
694 return RM_USER_DECLINED;
697 if (x->verbose)
698 printf (_("removing all entries of directory %s\n"),
699 quote (full_filename (dir_name)));
701 /* Save cwd if needed. */
702 if (need_save_cwd && save_cwd (&cwd))
703 return RM_ERROR;
705 /* Make target directory the current one. */
706 if (chdir (dir_name) < 0)
708 error (0, errno, _("cannot change to directory %s"),
709 quote (full_filename (dir_name)));
710 if (need_save_cwd)
711 free_cwd (&cwd);
712 return RM_ERROR;
715 /* Verify that the device and inode numbers of `.' are the same as
716 the ones we recorded for dir_name before we cd'd into it. This
717 detects the scenario in which an attacker tries to make Bob's rm
718 command remove some other directory belonging to Bob. The method
719 would be to replace an existing lstat'd but-not-yet-removed directory
720 with a symlink to the target directory. */
722 struct stat sb;
723 if (lstat (".", &sb))
724 error (EXIT_FAILURE, errno,
725 _("cannot lstat `.' in %s"), quote (full_filename (dir_name)));
727 assert (fs->have_device);
728 if (!SAME_INODE (sb, *fs))
730 error (EXIT_FAILURE, 0,
731 _("ERROR: the directory %s initially had device/inode\n\
732 numbers %lu/%lu, but now (after a chdir into it), the numbers for `.'\n\
733 are %lu/%lu. That means that while rm was running, the directory\n\
734 was replaced with either another directory or a link to another directory."),
735 quote (full_filename (dir_name)),
736 (unsigned long)(fs->st_dev),
737 (unsigned long)(fs->st_ino),
738 (unsigned long)(sb.st_dev),
739 (unsigned long)(sb.st_ino));
743 push_dir (dir_name);
745 /* Save a copy of dir_name. Otherwise, remove_cwd_entries may clobber
746 it because it is just a pointer to the dir entry's d_name field, and
747 remove_cwd_entries may close the directory. */
748 ASSIGN_STRDUPA (dir_name, dir_name);
750 status = remove_cwd_entries (x);
752 pop_dir ();
754 /* Restore cwd. */
755 if (need_save_cwd)
757 if (restore_cwd (&cwd, NULL, NULL))
759 free_cwd (&cwd);
760 return RM_ERROR;
762 free_cwd (&cwd);
764 else if (chdir ("..") < 0)
766 error (0, errno, _("cannot change back to directory %s via `..'"),
767 quote (full_filename (dir_name)));
768 return RM_ERROR;
771 if (x->interactive)
773 fprintf (stderr, _("%s: remove directory %s%s? "),
774 program_name,
775 quote (full_filename (dir_name)),
776 (status != RM_OK ? _(" (might be nonempty)") : ""));
777 if (!yesno ())
779 return RM_USER_DECLINED;
783 if (x->verbose)
784 printf (_("removing the directory itself: %s\n"),
785 quote (full_filename (dir_name)));
787 if (rmdir (dir_name) && (errno != ENOENT || !x->ignore_missing_files))
789 int saved_errno = errno;
791 #ifndef EINVAL
792 # define EINVAL 0
793 #endif
794 /* See if rmdir just failed because DIR_NAME is the current directory.
795 If so, give a better diagnostic than `rm: cannot remove directory
796 `...': Invalid argument' */
797 if (errno == EINVAL && same_file (".", dir_name))
799 error (0, 0, _("cannot remove current directory %s"),
800 quote (full_filename (dir_name)));
802 else
804 error (0, saved_errno, _("cannot remove directory %s"),
805 quote (full_filename (dir_name)));
807 return RM_ERROR;
810 return status;
813 /* Remove the file or directory specified by FS after checking appropriate
814 things. Return RM_OK if it is removed, and RM_ERROR or RM_USER_DECLINED
815 if not. If USER_SPECIFIED_NAME is non-zero, then the name part of FS may
816 be `.', `..', or may contain slashes. Otherwise, it must be a simple file
817 name (and hence must specify a file in the current directory). */
819 enum RM_status
820 rm (struct File_spec *fs, int user_specified_name, const struct rm_options *x)
822 mode_t filetype_mode;
824 if (user_specified_name)
826 /* CAUTION: this use of base_name works only because any
827 trailing slashes in fs->filename have already been removed. */
828 char *base = base_name (fs->filename);
830 if (DOT_OR_DOTDOT (base))
832 error (0, 0, _("cannot remove `.' or `..'"));
833 return RM_ERROR;
837 if (fspec_get_filetype_mode (fs, &filetype_mode))
839 if (x->ignore_missing_files && errno == ENOENT)
840 return RM_OK;
842 error (0, errno, _("cannot remove %s"),
843 quote (full_filename (fs->filename)));
844 return RM_ERROR;
847 #ifdef ENABLE_CYCLE_CHECK
848 if (S_ISDIR (filetype_mode))
850 struct active_dir_ent *old_ent;
851 struct active_dir_ent *new_ent;
853 /* If there is already a directory in the map with the same device
854 and inode numbers, then there is a directory cycle. */
856 if (fspec_get_device_number (fs))
858 error (0, errno, _("cannot stat %s"),
859 quote (full_filename (fs->filename)));
860 return RM_ERROR;
862 new_ent = make_active_dir_ent (fs->st_ino, fs->st_dev, current_depth ());
863 old_ent = hash_lookup (active_dir_map, new_ent);
864 if (old_ent)
866 error (0, 0, _("\
867 WARNING: Circular directory structure.\n\
868 This almost certainly means that you have a corrupted file system.\n\
869 NOTIFY YOUR SYSTEM MANAGER.\n\
870 The following two directories have the same inode number:\n"));
871 print_nth_dir (stderr, old_ent->depth);
872 fprintf (stderr, "\n%s\n", quote (full_filename (fs->filename)));
873 fflush (stderr);
875 if (x->interactive)
877 error (0, 0, _("continue? "));
878 if (yesno ())
879 return RM_ERROR;
881 exit (1);
884 /* Put this directory in the active_dir_map. */
885 if (! hash_insert (active_dir_map, new_ent))
886 xalloc_die ();
888 #endif
890 if (!S_ISDIR (filetype_mode) || x->unlink_dirs)
892 return remove_file (fs, x);
894 else
896 int need_save_cwd = user_specified_name;
897 enum RM_status status;
899 if (need_save_cwd)
900 need_save_cwd = (strchr (fs->filename, '/') != NULL);
902 status = remove_dir (fs, need_save_cwd, x);
904 #ifdef ENABLE_CYCLE_CHECK
906 struct active_dir_ent tmp;
907 struct active_dir_ent *old_ent;
909 /* Remove this directory from the active_dir_map. */
910 tmp.st_ino = fs->st_ino;
911 assert (fs->have_device);
912 tmp.st_dev = fs->st_dev;
913 old_ent = hash_delete (active_dir_map, &tmp);
914 assert (old_ent != NULL);
915 free (old_ent);
917 #endif
919 return status;
923 void
924 remove_init (void)
926 /* Initialize dir-stack obstacks. */
927 obstack_init (&dir_stack);
928 obstack_init (&len_stack);
930 #ifdef ENABLE_CYCLE_CHECK
931 active_dir_map = hash_initialize (ACTIVE_DIR_INITIAL_CAPACITY, NULL,
932 hash_active_dir_ent,
933 hash_compare_active_dir_ents, free);
934 #endif
937 void
938 remove_fini (void)
940 #ifdef ENABLE_CYCLE_CHECK
941 hash_free (active_dir_map);
942 #endif
944 obstack_free (&dir_stack, NULL);
945 obstack_free (&len_stack, NULL);