merge from rm-fix branch
[platform/upstream/coreutils.git] / src / rm.c
1 /* `rm' file deletion utility for GNU.
2    Copyright (C) 88, 90, 91, 94, 95, 96, 1997 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */
17
18 /* Written by Paul Rubin, David MacKenzie, and Richard Stallman.
19    Reworked to use chdir and hash tables by Jim Meyering.  */
20
21 #include <config.h>
22 #include <stdio.h>
23 #include <getopt.h>
24 #include <sys/types.h>
25 #include <assert.h>
26
27 #include "save-cwd.h"
28 #include "system.h"
29 #include "error.h"
30 #include "obstack.h"
31 #include "oa-hash.h"
32
33 #define obstack_chunk_alloc malloc
34 #define obstack_chunk_free free
35
36 #ifdef D_INO_IN_DIRENT
37 # define D_INO(dp) ((dp)->d_ino)
38 #else
39 /* Some systems don't have inodes, so fake them to avoid lots of ifdefs.  */
40 # define D_INO(dp) 1
41 #endif
42
43 #if !defined (S_ISLNK)
44 # define S_ISLNK(Mode) 0
45 #endif
46
47 #define DOT_OR_DOTDOT(Basename) \
48   (Basename[0] == '.' && (Basename[1] == '\0' \
49                           || (Basename[1] == '.' && Basename[2] == '\0')))
50
51 #if defined strdupa
52 # define ASSIGN_STRDUPA(DEST, S)                \
53   do { DEST = strdupa(S); } while (0)
54 #else
55 # define ASSIGN_STRDUPA(DEST, S)                \
56   do                                            \
57     {                                           \
58       size_t len_ = strlen (S) + 1;             \
59       char *tmp_dest_ = alloca (len_);          \
60       memcpy (tmp_dest_, (S), len_);            \
61       DEST = tmp_dest_;                         \
62     }                                           \
63   while (0)
64 #endif
65
66 enum RM_status
67 {
68   /* FIXME: describe and explain ordering: `ok' increasing in seriousness. */
69   RM_OK = 2,
70   RM_USER_DECLINED,
71   RM_ERROR
72 };
73
74 #define VALID_STATUS(S) \
75   ((S) == RM_OK || (S) == RM_USER_DECLINED || (S) == RM_ERROR)
76
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
80
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
84
85 struct File_spec
86 {
87   char *filename;
88   unsigned int have_filetype_mode:1;
89   unsigned int have_full_mode:1;
90   mode_t mode;
91   ino_t inum;
92 };
93
94 #ifndef STDC_HEADERS
95 void free ();
96 char *malloc ();
97 #endif
98
99 char *base_name ();
100 int euidaccess ();
101 char *stpcpy ();
102 char *stpncpy ();
103 void strip_trailing_slashes ();
104 char *xmalloc ();
105 char *xrealloc ();
106 int yesno ();
107
108 static enum RM_status rm (struct File_spec *fs, int user_specified_name);
109
110 /* Name this program was run with.  */
111 char *program_name;
112
113 /* If nonzero, display the name of each file removed.  */
114 static int verbose;
115
116 /* If nonzero, ignore nonexistant files.  */
117 static int ignore_missing_files;
118
119 /* If nonzero, recursively remove directories.  */
120 static int recursive;
121
122 /* If nonzero, query the user about whether to remove each file.  */
123 static int interactive;
124
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;
129
130 /* If nonzero, stdin is a tty.  */
131 static int stdin_tty;
132
133 /* If nonzero, display usage information and exit.  */
134 static int show_help;
135
136 /* If nonzero, print the version on standard output and exit.  */
137 static int show_version;
138
139 /* FIXME: describe */
140 static struct obstack dir_stack;
141
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;
147
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.
153
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;
161
162 /* An entry in the active_dir_map.  */
163 struct active_dir_ent
164 {
165   ino_t inum;
166   unsigned int depth;
167 };
168
169 static struct option const long_opts[] =
170 {
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},
178   {NULL, 0, NULL, 0}
179 };
180
181 static __inline unsigned int
182 current_depth (void)
183 {
184   return obstack_object_size (&len_stack) / sizeof (size_t);
185 }
186
187 static void
188 print_nth_dir (FILE *stream, unsigned int depth)
189 {
190   size_t *length = (size_t *) obstack_base (&len_stack);
191   char *dir_name = (char *) obstack_base (&dir_stack);
192   unsigned int sum = 0;
193   unsigned int i;
194
195   assert (0 <= depth && depth < current_depth ());
196
197   for (i = 0; i <= depth; i++)
198     {
199       sum += length[i];
200     }
201
202   fwrite (dir_name, 1, sum, stream);
203 }
204
205 static __inline struct active_dir_ent *
206 make_active_dir_ent (ino_t inum, unsigned int depth)
207 {
208   struct active_dir_ent *ent;
209   ent = (struct active_dir_ent *) xmalloc (sizeof *ent);
210   ent->inum = inum;
211   ent->depth = depth;
212   return ent;
213 }
214
215 static unsigned long
216 hash_active_dir_ent_1 (void const *x)
217 {
218   struct active_dir_ent const *ade = x;
219   return_INTEGER_HASH_1 (ade->inum);
220 }
221
222 static unsigned long
223 hash_active_dir_ent_2 (void const *x)
224 {
225   struct active_dir_ent const *ade = x;
226   return_INTEGER_HASH_2 (ade->inum);
227 }
228
229 static int
230 hash_compare_active_dir_ents (void const *x, void const *y)
231 {
232   struct active_dir_ent const *a = x;
233   struct active_dir_ent const *b = y;
234   return_INTEGER_COMPARE (a->inum, b->inum);
235 }
236
237 static unsigned long
238 hash_string_1 (void const *x)
239 {
240   return_STRING_HASH_1 (x);
241 }
242
243 static unsigned long
244 hash_string_2 (void const *x)
245 {
246   return_STRING_HASH_2 (x);
247 }
248
249 static int
250 hash_compare_strings (void const *x, void const *y)
251 {
252   return strcmp (x, y);
253 }
254
255 static void
256 usage (int status)
257 {
258   if (status != 0)
259     fprintf (stderr, _("Try `%s --help' for more information.\n"),
260              program_name);
261   else
262     {
263       printf (_("Usage: %s [OPTION]... FILE...\n"), program_name);
264       printf (_("\
265 Remove (unlink) the FILE(s).\n\
266 \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\
274 "));
275       puts (_("\nReport bugs to <fileutils-bugs@gnu.ai.mit.edu>."));
276     }
277   exit (status);
278 }
279
280 static __inline void
281 push_dir (const char *dir_name)
282 {
283   size_t len;
284
285   len = strlen (dir_name);
286
287   /* Append the string onto the stack.  */
288   obstack_grow (&dir_stack, dir_name, len);
289
290   /* Append a trailing slash.  */
291   obstack_1grow (&dir_stack, '/');
292
293   /* Add one for the slash.  */
294   ++len;
295
296   /* Push the length (including slash) onto its stack.  */
297   obstack_grow (&len_stack, &len, sizeof (len));
298 }
299
300 static __inline void
301 pop_dir (void)
302 {
303   int n_lengths = obstack_object_size (&len_stack) / sizeof (size_t);
304   size_t *length = (size_t *) obstack_base (&len_stack);
305   size_t top_len;
306
307   assert (n_lengths > 0);
308   top_len = length[n_lengths - 1];
309   assert (top_len >= 2);
310
311   /* Pop off the specified length of pathname.  */
312   assert (obstack_object_size (&dir_stack) >= top_len);
313   obstack_blank (&dir_stack, -top_len);
314
315   /* Pop the length stack, too.  */
316   assert (obstack_object_size (&len_stack) >= sizeof (size_t));
317   obstack_blank (&len_stack, -(sizeof (size_t)));
318 }
319
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.  */
325
326 static size_t
327 right_justify (char *dst, size_t dst_len, const char *src, size_t src_len,
328                char **result, int *truncated)
329 {
330   const char *sp;
331   char *dp;
332
333   if (src_len <= dst_len)
334     {
335       sp = src;
336       dp = dst + (dst_len - src_len);
337       *truncated = 0;
338     }
339   else
340     {
341       sp = src + (src_len - dst_len);
342       dp = dst;
343       src_len = dst_len;
344       *truncated = 1;
345     }
346
347   memcpy (dp, sp, src_len);
348
349   *result = dp;
350   return dst_len - src_len;
351 }
352
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.  */
357
358 static char *
359 full_filename (const char *filename)
360 {
361   static char *buf = NULL;
362   static size_t n_allocated = 0;
363
364   int dir_len = obstack_object_size (&dir_stack);
365   char *dir_name = (char *) obstack_base (&dir_stack);
366   size_t n_bytes_needed;
367   size_t filename_len;
368
369   filename_len = strlen (filename);
370   n_bytes_needed = dir_len + filename_len + 1;
371
372   if (n_bytes_needed > n_allocated)
373     {
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;
383
384       if (buf == NULL)
385         {
386 #define SBUF_SIZE 512
387 #define ELLIPSES_PREFIX "[...]"
388           static char static_buf[SBUF_SIZE];
389           int truncated;
390           size_t len;
391           char *p;
392
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);
396           if (truncated)
397             {
398               memcpy (static_buf, ELLIPSES_PREFIX,
399                       sizeof (ELLIPSES_PREFIX) - 1);
400             }
401           return p;
402         }
403     }
404
405   /* Copy directory part, including trailing slash.  */
406   memcpy (buf, dir_name, dir_len);
407
408   /* Append filename part, including trailing zero byte.  */
409   memcpy (buf + dir_len, filename, filename_len + 1);
410
411   assert (strlen (buf) + 1 == n_bytes_needed);
412
413   return buf;
414 }
415
416 static __inline void
417 fspec_init_file (struct File_spec *fs, const char *filename)
418 {
419   fs->filename = (char *) filename;
420   fs->have_full_mode = 0;
421   fs->have_filetype_mode = 0;
422 }
423
424 static __inline void
425 fspec_init_dp (struct File_spec *fs, struct dirent *dp)
426 {
427   fs->filename = dp->d_name;
428   fs->have_full_mode = 0;
429   fs->have_filetype_mode = 0;
430   fs->inum = D_INO (dp);
431
432 #if D_TYPE_IN_DIRENT && defined (DT_UNKNOWN)
433   if (filetype_mode != DT_UNKNOWN)
434     {
435       fs->have_filetype_mode = 1;
436       fs->mode = filetype_mode;
437     }
438 #endif
439 }
440
441 static __inline int
442 fspec_get_full_mode (struct File_spec *fs, mode_t *full_mode)
443 {
444   struct stat stat_buf;
445
446   if (fs->have_full_mode)
447     {
448       *full_mode = fs->mode;
449       return 0;
450     }
451
452   if (lstat (fs->filename, &stat_buf))
453     return 1;
454
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;
459
460   *full_mode = stat_buf.st_mode;
461   return 0;
462 }
463
464 static __inline int
465 fspec_get_filetype_mode (struct File_spec *fs, mode_t *filetype_mode)
466 {
467   int fail;
468
469   if (fs->have_filetype_mode)
470     {
471       *filetype_mode = fs->mode;
472       fail = 0;
473     }
474   else
475     {
476       fail = fspec_get_full_mode (fs, filetype_mode);
477     }
478
479   return fail;
480 }
481
482 static __inline mode_t
483 fspec_filetype_mode (const struct File_spec *fs)
484 {
485   assert (fs->have_filetype_mode);
486   return fs->mode;
487 }
488
489 /* Recursively remove all of the entries in the current directory.
490    Return an indication of the success of the operation.  */
491
492 enum RM_status
493 remove_cwd_entries (void)
494 {
495   /* NOTE: this is static.  */
496   static DIR *dirp = NULL;
497
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;
502
503   struct dirent *dp;
504   enum RM_status status = RM_OK;
505
506   if (dirp)
507     {
508       if (CLOSEDIR (dirp))
509         {
510           /* FIXME-someday: but this is actually the previously opened dir.  */
511           error (0, errno, "%s", full_filename ("."));
512           status = RM_ERROR;
513         }
514     }
515
516   do
517     {
518       errno = 0;
519       dirp = opendir (".");
520       if (dirp == NULL)
521         {
522           if (errno != ENOENT || !ignore_missing_files)
523             {
524               error (0, errno, "%s", full_filename ("."));
525               status = RM_ERROR;
526             }
527           break;
528         }
529
530       while ((dp = readdir (dirp)) != NULL)
531         {
532           /* Skip this entry if it's `.' or `..'.  */
533           if (DOT_OR_DOTDOT (dp->d_name))
534             continue;
535
536           /* Skip this entry if it's in the table of ones we've already
537              processed.  */
538           if (ht && hash_find_item (ht, (dp)->d_name))
539             continue;
540
541           {
542             struct File_spec fs;
543             enum RM_status tmp_status;
544             fspec_init_dp (&fs, dp);
545             tmp_status = rm (&fs, 0);
546             /* Update status.  */
547             if (tmp_status > status)
548               status = tmp_status;
549             assert (VALID_STATUS (status));
550           }
551
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.  */
555           if (status != RM_OK)
556             {
557               void *old_item;
558               char *p;
559               int fail;
560
561               if (ht == NULL)
562                 {
563                   ht = hash_init_table (NULL, HT_INITIAL_CAPACITY, 0, 0,
564                                         hash_string_1, hash_string_2,
565                                         hash_compare_strings);
566                   if (ht == NULL)
567                     error (1, 0, _("Memory exhausted"));
568                 }
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);
573               if (fail)
574                 error (1, 0, _("Memory exhausted"));
575             }
576
577           if (dirp == NULL)
578             break;
579         }
580     }
581   while (dirp == NULL);
582
583   if (CLOSEDIR (dirp))
584     {
585       error (0, errno, "%s", full_filename ("."));
586       status = 1;
587     }
588   dirp = NULL;
589
590   if (ht)
591     {
592       hash_free_items (ht);
593       hash_free_table (ht);
594       free (ht);
595     }
596
597   return status;
598 }
599
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.  */
604
605 static enum RM_status
606 remove_file (struct File_spec *fs)
607 {
608   int asked = 0;
609   char *pathname = fs->filename;
610
611   if (!ignore_missing_files && (interactive || stdin_tty)
612       && euidaccess (pathname, W_OK) )
613     {
614       if (!S_ISLNK (fspec_filetype_mode (fs)))
615         {
616           error (0, 0,
617                  (S_ISDIR (fspec_filetype_mode (fs))
618                   ? _("remove write-protected directory `%s'? ")
619                   : _("remove write-protected file `%s'? ")),
620                  full_filename (pathname));
621           if (!yesno ())
622             return RM_USER_DECLINED;
623
624           asked = 1;
625         }
626     }
627
628   if (!asked && interactive)
629     {
630       error (0, 0,
631              (S_ISDIR (fspec_filetype_mode (fs))
632               ? _("remove directory `%s'? ")
633               : _("remove `%s'? ")),
634              full_filename (pathname));
635       if (!yesno ())
636         return RM_USER_DECLINED;
637     }
638
639   if (verbose)
640     printf ("%s\n", full_filename (pathname));
641
642   if (unlink (pathname) && (errno != ENOENT || !ignore_missing_files))
643     {
644       error (0, errno, _("cannot unlink `%s'"), full_filename (pathname));
645       return RM_ERROR;
646     }
647   return RM_OK;
648 }
649
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.  */
655
656 static enum RM_status
657 remove_dir (struct File_spec *fs, int need_save_cwd)
658 {
659   enum RM_status status;
660   struct saved_cwd cwd;
661   char *dir_name = fs->filename;
662   const char *fmt = NULL;
663
664   if (!recursive)
665     {
666       error (0, 0, _("%s: is a directory"), full_filename (dir_name));
667       return RM_ERROR;
668     }
669
670   if (!ignore_missing_files && (interactive || stdin_tty)
671       && euidaccess (dir_name, W_OK))
672     {
673       fmt = _("directory `%s' is write protected; descend into it anyway? ");
674     }
675   else if (interactive)
676     {
677       fmt = _("descend into directory `%s'? ");
678     }
679
680   if (fmt)
681     {
682       error (0, 0, fmt, full_filename (dir_name));
683       if (!yesno ())
684         return RM_USER_DECLINED;
685     }
686
687   if (verbose)
688     printf ("%s\n", full_filename (dir_name));
689
690   /* Save cwd if needed.  */
691   if (need_save_cwd && save_cwd (&cwd))
692     return RM_ERROR;
693
694   /* Make target directory the current one.  */
695   if (chdir (dir_name) < 0)
696     {
697       error (0, errno, _("cannot change to directory %s"),
698              full_filename (dir_name));
699       if (need_save_cwd)
700         free_cwd (&cwd);
701       return RM_ERROR;
702     }
703
704   push_dir (dir_name);
705
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);
710
711   status = remove_cwd_entries ();
712
713   pop_dir ();
714
715   /* Restore cwd.  */
716   if (need_save_cwd)
717     {
718       if (restore_cwd (&cwd, NULL, NULL))
719         {
720           free_cwd (&cwd);
721           return RM_ERROR;
722         }
723       free_cwd (&cwd);
724     }
725   else if (chdir ("..") < 0)
726     {
727       error (0, errno, _("cannot change back to directory %s via `..'"),
728              full_filename (dir_name));
729       return RM_ERROR;
730     }
731
732   if (interactive)
733     {
734       error (0, 0, _("remove directory `%s'%s? "), full_filename (dir_name),
735              (status != RM_OK ? _(" (might be nonempty)") : ""));
736       if (!yesno ())
737         {
738           return RM_USER_DECLINED;
739         }
740     }
741
742   if (rmdir (dir_name) && (errno != ENOENT || !ignore_missing_files))
743     {
744       error (0, errno, _("cannot remove directory `%s'"),
745              full_filename (dir_name));
746       return RM_ERROR;
747     }
748
749   return RM_OK;
750 }
751
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).  */
757
758 static enum RM_status
759 rm (struct File_spec *fs, int user_specified_name)
760 {
761   mode_t filetype_mode;
762
763   if (user_specified_name)
764     {
765       char *base = base_name (fs->filename);
766
767       if (DOT_OR_DOTDOT (base))
768         {
769           error (0, 0, _("cannot remove `.' or `..'"));
770           return RM_ERROR;
771         }
772     }
773
774   if (fspec_get_filetype_mode (fs, &filetype_mode))
775     {
776       if (ignore_missing_files && errno == ENOENT)
777         return RM_OK;
778
779       error (0, errno, _("cannot remove `%s'"), full_filename (fs->filename));
780       return RM_ERROR;
781     }
782
783   if (S_ISDIR (filetype_mode))
784     {
785       int fail;
786       struct active_dir_ent *old_ent;
787
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 ()),
798                                (void **) &old_ent);
799       if (fail)
800         error (1, 0, _("Memory exhausted"));
801
802       if (old_ent)
803         {
804           error (0, 0, _("\
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);
813           fflush (stderr);
814
815           free (old_ent);
816
817           if (interactive)
818             {
819               error (0, 0, _("continue? "));
820               if (yesno ())
821                 return RM_ERROR;
822             }
823           exit (1);
824         }
825     }
826
827   if (!S_ISDIR (filetype_mode) || unlink_dirs)
828     {
829       return remove_file (fs);
830     }
831   else
832     {
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;
837
838       if (need_save_cwd)
839         need_save_cwd = (strchr (fs->filename, '/') != NULL);
840
841       status = remove_dir (fs, need_save_cwd);
842
843       /* Remove this directory from the active_dir_map.  */
844       tmp.inum = fs->inum;
845       hash_delete_item (active_dir_map, &tmp, (void **) &old_ent);
846       assert (old_ent != NULL);
847       free (old_ent);
848
849       return status;
850     }
851 }
852
853 int
854 main (int argc, char **argv)
855 {
856   int fail = 0;
857   int c;
858
859   program_name = argv[0];
860   setlocale (LC_ALL, "");
861   bindtextdomain (PACKAGE, LOCALEDIR);
862   textdomain (PACKAGE);
863
864   verbose = ignore_missing_files = recursive = interactive
865     = unlink_dirs = 0;
866
867   while ((c = getopt_long (argc, argv, "dfirvR", long_opts, NULL)) != -1)
868     {
869       switch (c)
870         {
871         case 0:         /* Long option.  */
872           break;
873         case 'd':
874           unlink_dirs = 1;
875           break;
876         case 'f':
877           interactive = 0;
878           ignore_missing_files = 1;
879           break;
880         case 'i':
881           interactive = 1;
882           ignore_missing_files = 0;
883           break;
884         case 'r':
885         case 'R':
886           recursive = 1;
887           break;
888         case 'v':
889           verbose = 1;
890           break;
891         default:
892           usage (1);
893         }
894     }
895
896   if (show_version)
897     {
898       printf ("rm (%s) %s\n", GNU_PACKAGE, VERSION);
899       exit (0);
900     }
901
902   if (show_help)
903     usage (0);
904
905   if (optind == argc)
906     {
907       if (ignore_missing_files)
908         exit (0);
909       else
910         {
911           error (0, 0, _("too few arguments"));
912           usage (1);
913         }
914     }
915
916   stdin_tty = isatty (STDIN_FILENO);
917
918   /* Initialize dir-stack obstacks.  */
919   obstack_init (&dir_stack);
920   obstack_init (&len_stack);
921
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);
926
927   for (; optind < argc; optind++)
928     {
929       struct File_spec fs;
930       enum RM_status status;
931
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)
939         fail = 1;
940     }
941
942   exit (fail);
943 }