summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorJim Meyering <jim@meyering.net>1998-01-22 08:13:57 +0000
committerJim Meyering <jim@meyering.net>1998-01-22 08:13:57 +0000
commitff59218d541edb2cc91d67c25033170308a458dc (patch)
treed1588fc775301a3ed983e61b583738b9cc7472d7 /src
parent1a18272561fbd7456e6914155246e5d02e48bd57 (diff)
downloadcoreutils-ff59218d541edb2cc91d67c25033170308a458dc.tar.xz
New file. Contains guts of old rm.c.
(remove_init): New function. (remove_fini): New function.
Diffstat (limited to 'src')
-rw-r--r--src/remove.c848
1 files changed, 848 insertions, 0 deletions
diff --git a/src/remove.c b/src/remove.c
new file mode 100644
index 000000000..125c5eaf5
--- /dev/null
+++ b/src/remove.c
@@ -0,0 +1,848 @@
+#include <config.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <assert.h>
+
+#include "save-cwd.h"
+#include "system.h"
+#include "closeout.h"
+#include "error.h"
+#include "obstack.h"
+#include "hash.h"
+#include "remove.h"
+
+#define obstack_chunk_alloc malloc
+#define obstack_chunk_free free
+
+#ifndef PARAMS
+# if defined (__GNUC__) || __STDC__
+# define PARAMS(args) args
+# else
+# define PARAMS(args) ()
+# endif
+#endif
+
+#ifdef D_INO_IN_DIRENT
+# define D_INO(dp) ((dp)->d_ino)
+# define ENABLE_CYCLE_CHECK
+#else
+/* Some systems don't have inodes, so fake them to avoid lots of ifdefs. */
+# define D_INO(dp) 1
+#endif
+
+#if !defined (S_ISLNK)
+# define S_ISLNK(Mode) 0
+#endif
+
+#define DOT_OR_DOTDOT(Basename) \
+ (Basename[0] == '.' && (Basename[1] == '\0' \
+ || (Basename[1] == '.' && Basename[2] == '\0')))
+
+#if defined strdupa
+# define ASSIGN_STRDUPA(DEST, S) \
+ do { DEST = strdupa(S); } while (0)
+#else
+# define ASSIGN_STRDUPA(DEST, S) \
+ do \
+ { \
+ const char *s_ = (S); \
+ size_t len_ = strlen (s_) + 1; \
+ char *tmp_dest_ = (char *) alloca (len_); \
+ DEST = memcpy (tmp_dest_, (s_), len_); \
+ } \
+ while (0)
+#endif
+
+/* Initial capacity of per-directory hash table of entries that have
+ been processed but not been deleted. */
+#define HT_INITIAL_CAPACITY 13
+
+/* Initial capacity of the active directory hash table. This table will
+ be resized only for hierarchies more than about 45 levels deep. */
+#define ACTIVE_DIR_INITIAL_CAPACITY 53
+
+char *base_name ();
+int euidaccess ();
+int yesno ();
+
+extern char *program_name;
+
+/* state initialized by remove_init, freed by remove_fini */
+
+/* An entry in the active_dir_map. */
+struct active_dir_ent
+{
+ ino_t inum;
+ unsigned int depth;
+};
+
+/* The name of the directory (starting with and relative to a command
+ line argument) being processed. When a subdirectory is entered, a new
+ component is appended (pushed). When RM chdir's out of a directory,
+ the top component is removed (popped). This is used to form a full
+ file name when necessary. */
+static struct obstack dir_stack;
+
+/* Stack of lengths of directory names (including trailing slash)
+ appended to dir_stack. We have to have a separate stack of lengths
+ (rather than just popping back to previous slash) because the first
+ element pushed onto the dir stack may contain slashes. */
+static struct obstack len_stack;
+
+/* Set of `active' directories from the current command-line argument
+ to the level in the hierarchy at which files are being removed.
+ A directory is added to the active set when RM begins removing it
+ (or its entries), and it is removed from the set just after RM has
+ finished processing it.
+
+ This is actually a map (not a set), implemented with a hash table.
+ For each active directory, it maps the directory's inode number to the
+ depth of that directory relative to the root of the tree being deleted.
+ A directory specified on the command line has depth zero.
+ This construct is used to detect directory cycles so that RM can warn
+ about them rather than iterating endlessly. */
+#ifdef ENABLE_CYCLE_CHECK
+static struct HT *active_dir_map;
+#endif
+
+static inline unsigned int
+current_depth (void)
+{
+ return obstack_object_size (&len_stack) / sizeof (size_t);
+}
+
+static void
+print_nth_dir (FILE *stream, unsigned int depth)
+{
+ size_t *length = (size_t *) obstack_base (&len_stack);
+ char *dir_name = (char *) obstack_base (&dir_stack);
+ unsigned int sum = 0;
+ unsigned int i;
+
+ assert (0 <= depth && depth < current_depth ());
+
+ for (i = 0; i <= depth; i++)
+ {
+ sum += length[i];
+ }
+
+ fwrite (dir_name, 1, sum, stream);
+}
+
+static inline struct active_dir_ent *
+make_active_dir_ent (ino_t inum, unsigned int depth)
+{
+ struct active_dir_ent *ent;
+ ent = (struct active_dir_ent *) xmalloc (sizeof *ent);
+ ent->inum = inum;
+ ent->depth = depth;
+ return ent;
+}
+
+static unsigned int
+hash_active_dir_ent (void const *x, unsigned int table_size)
+{
+ struct active_dir_ent const *ade = x;
+ return ade->inum % table_size;
+}
+
+static int
+hash_compare_active_dir_ents (void const *x, void const *y)
+{
+ struct active_dir_ent const *a = x;
+ struct active_dir_ent const *b = y;
+ return (a->inum == b->inum ? 0 : 1);
+}
+
+/* A hash function for null-terminated char* strings using
+ the method described in Aho, Sethi, & Ullman, p 436. */
+
+static unsigned int
+hash_pjw (const void *x, unsigned int tablesize)
+{
+ const char *s = x;
+ unsigned int h = 0;
+ unsigned int g;
+
+ while (*s != 0)
+ {
+ h = (h << 4) + *s++;
+ if ((g = h & (unsigned int) 0xf0000000) != 0)
+ h = (h ^ (g >> 24)) ^ g;
+ }
+
+ return (h % tablesize);
+}
+
+static int
+hash_compare_strings (void const *x, void const *y)
+{
+ return strcmp (x, y);
+}
+
+static inline void
+push_dir (const char *dir_name)
+{
+ size_t len;
+
+ len = strlen (dir_name);
+
+ /* Append the string onto the stack. */
+ obstack_grow (&dir_stack, dir_name, len);
+
+ /* Append a trailing slash. */
+ obstack_1grow (&dir_stack, '/');
+
+ /* Add one for the slash. */
+ ++len;
+
+ /* Push the length (including slash) onto its stack. */
+ obstack_grow (&len_stack, &len, sizeof (len));
+}
+
+static inline void
+pop_dir (void)
+{
+ int n_lengths = obstack_object_size (&len_stack) / sizeof (size_t);
+ size_t *length = (size_t *) obstack_base (&len_stack);
+ size_t top_len;
+
+ assert (n_lengths > 0);
+ top_len = length[n_lengths - 1];
+ assert (top_len >= 2);
+
+ /* Pop off the specified length of pathname. */
+ assert (obstack_object_size (&dir_stack) >= top_len);
+ obstack_blank (&dir_stack, -top_len);
+
+ /* Pop the length stack, too. */
+ assert (obstack_object_size (&len_stack) >= sizeof (size_t));
+ obstack_blank (&len_stack, -(sizeof (size_t)));
+}
+
+/* Copy the SRC_LEN bytes of data beginning at SRC into the DST_LEN-byte
+ buffer, DST, so that the last source byte is at the end of the destination
+ buffer. If SRC_LEN is longer than DST_LEN, then set *TRUNCATED to non-zero.
+ Set *RESULT to point to the beginning of (the portion of) the source data
+ in DST. Return the number of bytes remaining in the destination buffer. */
+
+static size_t
+right_justify (char *dst, size_t dst_len, const char *src, size_t src_len,
+ char **result, int *truncated)
+{
+ const char *sp;
+ char *dp;
+
+ if (src_len <= dst_len)
+ {
+ sp = src;
+ dp = dst + (dst_len - src_len);
+ *truncated = 0;
+ }
+ else
+ {
+ sp = src + (src_len - dst_len);
+ dp = dst;
+ src_len = dst_len;
+ *truncated = 1;
+ }
+
+ *result = memcpy (dp, sp, src_len);
+ return dst_len - src_len;
+}
+
+/* Using the global directory name obstack, create the full path to FILENAME.
+ Return it in sometimes-realloc'd space that should not be freed by the
+ caller. Realloc as necessary. If realloc fails, use a static buffer
+ and put as long a suffix in that buffer as possible. */
+
+static char *
+full_filename (const char *filename)
+{
+ static char *buf = NULL;
+ static size_t n_allocated = 0;
+
+ int dir_len = obstack_object_size (&dir_stack);
+ char *dir_name = (char *) obstack_base (&dir_stack);
+ size_t n_bytes_needed;
+ size_t filename_len;
+
+ filename_len = strlen (filename);
+ n_bytes_needed = dir_len + filename_len + 1;
+
+ if (n_bytes_needed > n_allocated)
+ {
+ /* This code requires that realloc accept NULL as the first arg.
+ This function must not use xrealloc. Otherwise, an out-of-memory
+ error involving a file name to be expanded here wouldn't ever
+ be issued. Use realloc and fall back on using a static buffer
+ if memory allocation fails. */
+ buf = realloc (buf, n_bytes_needed);
+ n_allocated = n_bytes_needed;
+
+ if (buf == NULL)
+ {
+#define SBUF_SIZE 512
+#define ELLIPSES_PREFIX "[...]"
+ static char static_buf[SBUF_SIZE];
+ int truncated;
+ size_t len;
+ char *p;
+
+ len = right_justify (static_buf, SBUF_SIZE, filename,
+ filename_len + 1, &p, &truncated);
+ right_justify (static_buf, len, dir_name, dir_len, &p, &truncated);
+ if (truncated)
+ {
+ memcpy (static_buf, ELLIPSES_PREFIX,
+ sizeof (ELLIPSES_PREFIX) - 1);
+ }
+ return p;
+ }
+ }
+
+ /* Copy directory part, including trailing slash, and then
+ append the filename part, including a trailing zero byte. */
+ memcpy (mempcpy (buf, dir_name, dir_len), filename, filename_len + 1);
+
+ assert (strlen (buf) + 1 == n_bytes_needed);
+
+ return buf;
+}
+
+void
+fspec_init_file (struct File_spec *fs, const char *filename)
+{
+ fs->filename = (char *) filename;
+ fs->have_full_mode = 0;
+ fs->have_filetype_mode = 0;
+}
+
+static inline void
+fspec_init_dp (struct File_spec *fs, struct dirent *dp)
+{
+ fs->filename = dp->d_name;
+ fs->have_full_mode = 0;
+ fs->have_filetype_mode = 0;
+ fs->inum = D_INO (dp);
+
+#if D_TYPE_IN_DIRENT && defined (DT_UNKNOWN) && defined (DTTOIF)
+ if (dp->d_type != DT_UNKNOWN)
+ {
+ fs->have_filetype_mode = 1;
+ fs->mode = DTTOIF (dp->d_type);
+ }
+#endif
+}
+
+static inline int
+fspec_get_full_mode (struct File_spec *fs, mode_t *full_mode)
+{
+ struct stat stat_buf;
+
+ if (fs->have_full_mode)
+ {
+ *full_mode = fs->mode;
+ return 0;
+ }
+
+ if (lstat (fs->filename, &stat_buf))
+ return 1;
+
+ fs->have_full_mode = 1;
+ fs->have_filetype_mode = 1;
+ fs->mode = stat_buf.st_mode;
+ fs->inum = stat_buf.st_ino;
+
+ *full_mode = stat_buf.st_mode;
+ return 0;
+}
+
+static inline int
+fspec_get_filetype_mode (struct File_spec *fs, mode_t *filetype_mode)
+{
+ int fail;
+
+ if (fs->have_filetype_mode)
+ {
+ *filetype_mode = fs->mode;
+ fail = 0;
+ }
+ else
+ {
+ fail = fspec_get_full_mode (fs, filetype_mode);
+ }
+
+ return fail;
+}
+
+static inline mode_t
+fspec_filetype_mode (const struct File_spec *fs)
+{
+ assert (fs->have_filetype_mode);
+ return fs->mode;
+}
+
+/* Recursively remove all of the entries in the current directory.
+ Return an indication of the success of the operation. */
+
+enum RM_status
+remove_cwd_entries (const struct rm_options *x)
+{
+ /* NOTE: this is static. */
+ static DIR *dirp = NULL;
+
+ /* NULL or a malloc'd and initialized hash table of entries in the
+ current directory that have been processed but not removed --
+ due either to an error or to an interactive `no' response. */
+ struct HT *ht = NULL;
+
+ /* FIXME: describe */
+ static struct obstack entry_name_pool;
+ static int first_call = 1;
+
+ enum RM_status status = RM_OK;
+
+ if (first_call)
+ {
+ first_call = 0;
+ obstack_init (&entry_name_pool);
+ }
+
+ if (dirp)
+ {
+ if (CLOSEDIR (dirp))
+ {
+ /* FIXME-someday: but this is actually the previously opened dir. */
+ error (0, errno, "%s", full_filename ("."));
+ status = RM_ERROR;
+ }
+ dirp = NULL;
+ }
+
+ do
+ {
+ /* FIXME: why do this? */
+ errno = 0;
+
+ dirp = opendir (".");
+ if (dirp == NULL)
+ {
+ if (errno != ENOENT || !x->ignore_missing_files)
+ {
+ error (0, errno, "%s", full_filename ("."));
+ status = RM_ERROR;
+ }
+ break;
+ }
+
+ while (1)
+ {
+ char *entry_name;
+ struct File_spec fs;
+ enum RM_status tmp_status;
+ struct dirent *dp;
+
+/* FILE should be skipped if it is `.' or `..', or if it is in
+ the table, HT, of entries we've already processed. */
+#define SKIPPABLE(Ht, File) (DOT_OR_DOTDOT(File) \
+ || (Ht && hash_query_in_table (Ht, File)))
+
+ /* FIXME: use readdir_r directly into an obstack to avoid
+ the obstack_copy0 below --
+ Suggestion from Uli. Be careful -- there are different
+ prototypes on e.g. Solaris.
+
+ Do something like this:
+ #define NAME_MAX_FOR(Parent_dir) pathconf ((Parent_dir),
+ _PC_NAME_MAX);
+ dp = obstack_alloc (sizeof (struct dirent)
+ + NAME_MAX_FOR (".") + 1);
+ fail = xreaddir (dirp, dp);
+ where xreaddir is ...
+
+ But what about systems like the hurd where NAME_MAX is supposed
+ to be effectively unlimited. We don't want to have to allocate
+ a huge buffer to accommodate maximum possible entry name. */
+
+ dp = readdir (dirp);
+
+#if ! HAVE_WORKING_READDIR
+ if (dp == NULL)
+ {
+ /* Since we have probably modified the directory since it
+ was opened, readdir returning NULL does not necessarily
+ mean we have read the last entry. Rewind it and check
+ again. This happens on SunOS4.1.4 with 254 or more files
+ in a directory. */
+ rewinddir (dirp);
+ while ((dp = readdir (dirp)) && SKIPPABLE (ht, dp->d_name))
+ {
+ /* empty */
+ }
+ }
+#endif
+
+ if (dp == NULL)
+ break;
+
+ if (SKIPPABLE (ht, dp->d_name))
+ continue;
+
+ fspec_init_dp (&fs, dp);
+
+ /* Save a copy of the name of this entry, in case we have
+ to add it to the set of unremoved entries below. */
+ entry_name = obstack_copy0 (&entry_name_pool,
+ dp->d_name, NLENGTH (dp));
+
+ /* CAUTION: after this call to rm, DP may not be valid --
+ it may have been freed due to a close in a recursive call
+ (through rm and remove_dir) to this function. */
+ tmp_status = rm (&fs, 0, x);
+
+ /* Update status. */
+ if (tmp_status > status)
+ status = tmp_status;
+ assert (VALID_STATUS (status));
+
+ /* If this entry was not removed (due either to an error or to
+ an interactive `no' response), record it in the hash table so
+ we don't consider it again if we reopen this directory later. */
+ if (status != RM_OK)
+ {
+ int fail;
+
+ if (ht == NULL)
+ {
+ ht = hash_initialize (HT_INITIAL_CAPACITY, NULL,
+ hash_pjw, hash_compare_strings);
+ if (ht == NULL)
+ error (1, 0, _("Memory exhausted"));
+ }
+ HASH_INSERT_NEW_ITEM (ht, entry_name, &fail);
+ if (fail)
+ error (1, 0, _("Memory exhausted"));
+ }
+ else
+ {
+ /* This entry was not saved in the hash table. Free it. */
+ obstack_free (&entry_name_pool, entry_name);
+ }
+
+ if (dirp == NULL)
+ break;
+ }
+ }
+ while (dirp == NULL);
+
+ if (CLOSEDIR (dirp))
+ {
+ error (0, errno, "%s", full_filename ("."));
+ status = 1;
+ }
+ dirp = NULL;
+
+ if (ht)
+ {
+ hash_free (ht);
+ }
+
+ if (obstack_object_size (&entry_name_pool) > 0)
+ obstack_free (&entry_name_pool, obstack_base (&entry_name_pool));
+
+ return status;
+}
+
+/* Query the user if appropriate, and if ok try to remove the
+ file or directory specified by FS. Return RM_OK if it is removed,
+ and RM_ERROR or RM_USER_DECLINED if not. */
+
+static enum RM_status
+remove_file (struct File_spec *fs, const struct rm_options *x)
+{
+ int asked = 0;
+ char *pathname = fs->filename;
+
+ if (!x->ignore_missing_files && (x->interactive || x->stdin_tty)
+ && euidaccess (pathname, W_OK))
+ {
+ if (!S_ISLNK (fspec_filetype_mode (fs)))
+ {
+ fprintf (stderr,
+ (S_ISDIR (fspec_filetype_mode (fs))
+ ? _("%s: remove write-protected directory `%s'? ")
+ : _("%s: remove write-protected file `%s'? ")),
+ program_name, full_filename (pathname));
+ if (!yesno ())
+ return RM_USER_DECLINED;
+
+ asked = 1;
+ }
+ }
+
+ if (!asked && x->interactive)
+ {
+ /* FIXME: use a variant of error (instead of fprintf) that doesn't
+ append a newline. Then we won't have to declare program_name in
+ this file. */
+ fprintf (stderr,
+ (S_ISDIR (fspec_filetype_mode (fs))
+ ? _("%s: remove directory `%s'? ")
+ : _("%s: remove `%s'? ")),
+ program_name, full_filename (pathname));
+ if (!yesno ())
+ return RM_USER_DECLINED;
+ }
+
+ if (x->verbose)
+ printf (_("removing non-directory %s\n"), full_filename (pathname));
+
+ if (unlink (pathname) && (errno != ENOENT || !x->ignore_missing_files))
+ {
+ error (0, errno, _("cannot unlink `%s'"), full_filename (pathname));
+ return RM_ERROR;
+ }
+ return RM_OK;
+}
+
+/* If not in recursive mode, print an error message and return RM_ERROR.
+ Otherwise, query the user if appropriate, then try to recursively
+ remove the directory specified by FS. Return RM_OK if it is removed,
+ and RM_ERROR or RM_USER_DECLINED if not.
+ FIXME: describe need_save_cwd parameter. */
+
+static enum RM_status
+remove_dir (struct File_spec *fs, int need_save_cwd, const struct rm_options *x)
+{
+ enum RM_status status;
+ struct saved_cwd cwd;
+ char *dir_name = fs->filename;
+ const char *fmt = NULL;
+
+ if (!x->recursive)
+ {
+ error (0, 0, _("%s: is a directory"), full_filename (dir_name));
+ return RM_ERROR;
+ }
+
+ if (!x->ignore_missing_files && (x->interactive || x->stdin_tty)
+ && euidaccess (dir_name, W_OK))
+ {
+ fmt = _("%s: directory `%s' is write protected; descend into it anyway? ");
+ }
+ else if (x->interactive)
+ {
+ fmt = _("%s: descend into directory `%s'? ");
+ }
+
+ if (fmt)
+ {
+ fprintf (stderr, fmt, program_name, full_filename (dir_name));
+ if (!yesno ())
+ return RM_USER_DECLINED;
+ }
+
+ if (x->verbose)
+ printf (_("removing any entries of directory %s\n"),
+ full_filename (dir_name));
+
+ /* Save cwd if needed. */
+ if (need_save_cwd && save_cwd (&cwd))
+ return RM_ERROR;
+
+ /* Make target directory the current one. */
+ if (chdir (dir_name) < 0)
+ {
+ error (0, errno, _("cannot change to directory %s"),
+ full_filename (dir_name));
+ if (need_save_cwd)
+ free_cwd (&cwd);
+ return RM_ERROR;
+ }
+
+ push_dir (dir_name);
+
+ /* Save a copy of dir_name. Otherwise, remove_cwd_entries may clobber
+ it because it is just a pointer to the dir entry's d_name field, and
+ remove_cwd_entries may close the directory. */
+ ASSIGN_STRDUPA (dir_name, dir_name);
+
+ status = remove_cwd_entries (x);
+
+ pop_dir ();
+
+ /* Restore cwd. */
+ if (need_save_cwd)
+ {
+ if (restore_cwd (&cwd, NULL, NULL))
+ {
+ free_cwd (&cwd);
+ return RM_ERROR;
+ }
+ free_cwd (&cwd);
+ }
+ else if (chdir ("..") < 0)
+ {
+ error (0, errno, _("cannot change back to directory %s via `..'"),
+ full_filename (dir_name));
+ return RM_ERROR;
+ }
+
+ if (x->interactive)
+ {
+ error (0, 0, _("remove directory `%s'%s? "), full_filename (dir_name),
+ (status != RM_OK ? _(" (might be nonempty)") : ""));
+ if (!yesno ())
+ {
+ return RM_USER_DECLINED;
+ }
+ }
+
+ if (x->verbose)
+ printf (_("removing the directory itself: %s\n"), full_filename (dir_name));
+
+ if (rmdir (dir_name) && (errno != ENOENT || !x->ignore_missing_files))
+ {
+ error (0, errno, _("cannot remove directory `%s'"),
+ full_filename (dir_name));
+ return RM_ERROR;
+ }
+
+ return RM_OK;
+}
+
+/* Remove the file or directory specified by FS after checking appropriate
+ things. Return RM_OK if it is removed, and RM_ERROR or RM_USER_DECLINED
+ if not. If USER_SPECIFIED_NAME is non-zero, then the name part of FS may
+ be `.', `..', or may contain slashes. Otherwise, it must be a simple file
+ name (and hence must specify a file in the current directory). */
+
+enum RM_status
+rm (struct File_spec *fs, int user_specified_name, const struct rm_options *x)
+{
+ mode_t filetype_mode;
+
+ if (user_specified_name)
+ {
+ char *base = base_name (fs->filename);
+
+ if (DOT_OR_DOTDOT (base))
+ {
+ error (0, 0, _("cannot remove `.' or `..'"));
+ return RM_ERROR;
+ }
+ }
+
+ if (fspec_get_filetype_mode (fs, &filetype_mode))
+ {
+ if (x->ignore_missing_files && errno == ENOENT)
+ return RM_OK;
+
+ error (0, errno, _("cannot remove `%s'"), full_filename (fs->filename));
+ return RM_ERROR;
+ }
+
+#ifdef ENABLE_CYCLE_CHECK
+ if (S_ISDIR (filetype_mode))
+ {
+ int fail;
+ struct active_dir_ent *old_ent;
+
+ /* Insert this directory in the active_dir_map.
+ If there is already a directory in the map with the same inum,
+ then there's *probably* a directory cycle. This test can get
+ a false positive if two directories have the same inode number
+ but different device numbers and one directory contains the
+ other. But since people don't often try to delete hierarchies
+ containing mount points, and when they do, duplicate inode
+ numbers are not that likely, this isn't worth detecting. */
+ old_ent = hash_insert_if_absent (active_dir_map,
+ make_active_dir_ent (fs->inum,
+ current_depth ()),
+ &fail);
+ if (fail)
+ error (1, 0, _("Memory exhausted"));
+
+ if (old_ent)
+ {
+ error (0, 0, _("\
+WARNING: Circular directory structure.\n\
+This almost certainly means that you have a corrupted file system.\n\
+NOTIFY YOUR SYSTEM MANAGER.\n\
+The following two directories have the same inode number:\n"));
+ /* FIXME: test this!! */
+ print_nth_dir (stderr, current_depth ());
+ fputc ('\n', stderr);
+ print_nth_dir (stderr, old_ent->depth);
+ fputc ('\n', stderr);
+ fflush (stderr);
+
+ free (old_ent);
+
+ if (x->interactive)
+ {
+ error (0, 0, _("continue? "));
+ if (yesno ())
+ return RM_ERROR;
+ }
+ exit (1);
+ }
+ }
+#endif
+
+ if (!S_ISDIR (filetype_mode) || x->unlink_dirs)
+ {
+ return remove_file (fs, x);
+ }
+ else
+ {
+ int need_save_cwd = user_specified_name;
+ enum RM_status status;
+
+ if (need_save_cwd)
+ need_save_cwd = (strchr (fs->filename, '/') != NULL);
+
+ status = remove_dir (fs, need_save_cwd, x);
+
+#ifdef ENABLE_CYCLE_CHECK
+ {
+ struct active_dir_ent tmp;
+ struct active_dir_ent *old_ent;
+
+ /* Remove this directory from the active_dir_map. */
+ tmp.inum = fs->inum;
+ old_ent = hash_delete_if_present (active_dir_map, &tmp);
+ assert (old_ent != NULL);
+ free (old_ent);
+ }
+#endif
+
+ return status;
+ }
+}
+
+void
+remove_init (void)
+{
+ /* Initialize dir-stack obstacks. */
+ obstack_init (&dir_stack);
+ obstack_init (&len_stack);
+
+#ifdef ENABLE_CYCLE_CHECK
+ active_dir_map = hash_initialize (ACTIVE_DIR_INITIAL_CAPACITY, free,
+ hash_active_dir_ent,
+ hash_compare_active_dir_ents);
+#endif
+}
+
+void
+remove_fini (void)
+{
+#ifdef ENABLE_CYCLE_CHECK
+ hash_free (active_dir_map);
+#endif
+
+ obstack_free (&dir_stack, NULL);
+ obstack_free (&len_stack, NULL);
+}