s/__inline/inline/.
[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 #ifndef PARAMS
34 # if defined (__GNUC__) || __STDC__
35 #  define PARAMS(args) args
36 # else
37 #  define PARAMS(args) ()
38 # endif
39 #endif
40
41 #define obstack_chunk_alloc malloc
42 #define obstack_chunk_free free
43
44 #ifdef D_INO_IN_DIRENT
45 # define D_INO(dp) ((dp)->d_ino)
46 #else
47 /* Some systems don't have inodes, so fake them to avoid lots of ifdefs.  */
48 # define D_INO(dp) 1
49 #endif
50
51 #if !defined (S_ISLNK)
52 # define S_ISLNK(Mode) 0
53 #endif
54
55 #define DOT_OR_DOTDOT(Basename) \
56   (Basename[0] == '.' && (Basename[1] == '\0' \
57                           || (Basename[1] == '.' && Basename[2] == '\0')))
58
59 #if defined strdupa
60 # define ASSIGN_STRDUPA(DEST, S)                \
61   do { DEST = strdupa(S); } while (0)
62 #else
63 # define ASSIGN_STRDUPA(DEST, S)                \
64   do                                            \
65     {                                           \
66       size_t len_ = strlen (S) + 1;             \
67       char *tmp_dest_ = alloca (len_);          \
68       memcpy (tmp_dest_, (S), len_);            \
69       DEST = tmp_dest_;                         \
70     }                                           \
71   while (0)
72 #endif
73
74 enum RM_status
75 {
76   /* FIXME: describe and explain ordering: `ok' increasing in seriousness. */
77   RM_OK = 2,
78   RM_USER_DECLINED,
79   RM_ERROR
80 };
81
82 #define VALID_STATUS(S) \
83   ((S) == RM_OK || (S) == RM_USER_DECLINED || (S) == RM_ERROR)
84
85 /* Initial capacity of per-directory hash table of entries that have
86    been processed but not been deleted.  */
87 #define HT_INITIAL_CAPACITY 13
88
89 /* Initial capacity of the active directory hash table.  This table will
90    be resized only for hierarchies more than about 45 levels deep. */
91 #define ACTIVE_DIR_INITIAL_CAPACITY 53
92
93 struct File_spec
94 {
95   char *filename;
96   unsigned int have_filetype_mode:1;
97   unsigned int have_full_mode:1;
98   mode_t mode;
99   ino_t inum;
100 };
101
102 #ifndef STDC_HEADERS
103 void free ();
104 char *malloc ();
105 #endif
106
107 char *base_name ();
108 int euidaccess ();
109 char *stpcpy ();
110 char *stpncpy ();
111 void strip_trailing_slashes ();
112 char *xmalloc ();
113 char *xrealloc ();
114 int yesno ();
115
116 /* Forward dcl for recursively called function.  */
117 static enum RM_status rm PARAMS ((struct File_spec *fs,
118                                   int user_specified_name));
119
120 /* Name this program was run with.  */
121 char *program_name;
122
123 /* If nonzero, display the name of each file removed.  */
124 static int verbose;
125
126 /* If nonzero, ignore nonexistant files.  */
127 static int ignore_missing_files;
128
129 /* If nonzero, recursively remove directories.  */
130 static int recursive;
131
132 /* If nonzero, query the user about whether to remove each file.  */
133 static int interactive;
134
135 /* If nonzero, remove directories with unlink instead of rmdir, and don't
136    require a directory to be empty before trying to unlink it.
137    Only works for the super-user.  */
138 static int unlink_dirs;
139
140 /* If nonzero, stdin is a tty.  */
141 static int stdin_tty;
142
143 /* If nonzero, display usage information and exit.  */
144 static int show_help;
145
146 /* If nonzero, print the version on standard output and exit.  */
147 static int show_version;
148
149 /* FIXME: describe */
150 static struct obstack dir_stack;
151
152 /* Stack of lengths of directory names (including trailing slash)
153    appended to dir_stack.  We have to have a separate stack of lengths
154    (rather than just popping back to previous slash) because the first
155    element pushed onto the dir stack may contain slashes.  */
156 static struct obstack len_stack;
157
158 /* Set of `active' directories from the current command-line parameter
159    to the level in the hierarchy at which files are being removed.
160    A directory is added to the active set when RM begins removing it
161    (or its entries), and it is removed from the set just after RM has
162    finished processing it.
163
164    This is actually a map (not a set), implemented with a hash table.
165    For each active directory, it maps the directory's inode number to the
166    depth of that directory relative to the root of the tree being deleted.
167    A directory specified on the command line has depth zero.
168    This construct is used to detect directory cycles so that RM can warn
169    about them rather than iterating endlessly.  */
170 static struct hash_table *active_dir_map;
171
172 /* An entry in the active_dir_map.  */
173 struct active_dir_ent
174 {
175   ino_t inum;
176   unsigned int depth;
177 };
178
179 static struct option const long_opts[] =
180 {
181   {"directory", no_argument, &unlink_dirs, 1},
182   {"force", no_argument, NULL, 'f'},
183   {"interactive", no_argument, NULL, 'i'},
184   {"recursive", no_argument, &recursive, 1},
185   {"verbose", no_argument, &verbose, 1},
186   {"help", no_argument, &show_help, 1},
187   {"version", no_argument, &show_version, 1},
188   {NULL, 0, NULL, 0}
189 };
190
191 static inline unsigned int
192 current_depth (void)
193 {
194   return obstack_object_size (&len_stack) / sizeof (size_t);
195 }
196
197 static void
198 print_nth_dir (FILE *stream, unsigned int depth)
199 {
200   size_t *length = (size_t *) obstack_base (&len_stack);
201   char *dir_name = (char *) obstack_base (&dir_stack);
202   unsigned int sum = 0;
203   unsigned int i;
204
205   assert (0 <= depth && depth < current_depth ());
206
207   for (i = 0; i <= depth; i++)
208     {
209       sum += length[i];
210     }
211
212   fwrite (dir_name, 1, sum, stream);
213 }
214
215 static inline struct active_dir_ent *
216 make_active_dir_ent (ino_t inum, unsigned int depth)
217 {
218   struct active_dir_ent *ent;
219   ent = (struct active_dir_ent *) xmalloc (sizeof *ent);
220   ent->inum = inum;
221   ent->depth = depth;
222   return ent;
223 }
224
225 static unsigned long
226 hash_active_dir_ent_1 (void const *x)
227 {
228   struct active_dir_ent const *ade = x;
229   return_INTEGER_HASH_1 (ade->inum);
230 }
231
232 static unsigned long
233 hash_active_dir_ent_2 (void const *x)
234 {
235   struct active_dir_ent const *ade = x;
236   return_INTEGER_HASH_2 (ade->inum);
237 }
238
239 static int
240 hash_compare_active_dir_ents (void const *x, void const *y)
241 {
242   struct active_dir_ent const *a = x;
243   struct active_dir_ent const *b = y;
244   return_INTEGER_COMPARE (a->inum, b->inum);
245 }
246
247 static unsigned long
248 hash_string_1 (void const *x)
249 {
250   return_STRING_HASH_1 (x);
251 }
252
253 static unsigned long
254 hash_string_2 (void const *x)
255 {
256   return_STRING_HASH_2 (x);
257 }
258
259 static int
260 hash_compare_strings (void const *x, void const *y)
261 {
262   return strcmp (x, y);
263 }
264
265 static void
266 usage (int status)
267 {
268   if (status != 0)
269     fprintf (stderr, _("Try `%s --help' for more information.\n"),
270              program_name);
271   else
272     {
273       printf (_("Usage: %s [OPTION]... FILE...\n"), program_name);
274       printf (_("\
275 Remove (unlink) the FILE(s).\n\
276 \n\
277   -d, --directory       unlink directory, even if non-empty (super-user only)\n\
278   -f, --force           ignore nonexistent files, never prompt\n\
279   -i, --interactive     prompt before any removal\n\
280   -r, -R, --recursive   remove the contents of directories recursively\n\
281   -v, --verbose         explain what is being done\n\
282       --help            display this help and exit\n\
283       --version         output version information and exit\n\
284 "));
285       puts (_("\nReport bugs to <fileutils-bugs@gnu.ai.mit.edu>."));
286     }
287   exit (status);
288 }
289
290 static inline void
291 push_dir (const char *dir_name)
292 {
293   size_t len;
294
295   len = strlen (dir_name);
296
297   /* Append the string onto the stack.  */
298   obstack_grow (&dir_stack, dir_name, len);
299
300   /* Append a trailing slash.  */
301   obstack_1grow (&dir_stack, '/');
302
303   /* Add one for the slash.  */
304   ++len;
305
306   /* Push the length (including slash) onto its stack.  */
307   obstack_grow (&len_stack, &len, sizeof (len));
308 }
309
310 static inline void
311 pop_dir (void)
312 {
313   int n_lengths = obstack_object_size (&len_stack) / sizeof (size_t);
314   size_t *length = (size_t *) obstack_base (&len_stack);
315   size_t top_len;
316
317   assert (n_lengths > 0);
318   top_len = length[n_lengths - 1];
319   assert (top_len >= 2);
320
321   /* Pop off the specified length of pathname.  */
322   assert (obstack_object_size (&dir_stack) >= top_len);
323   obstack_blank (&dir_stack, -top_len);
324
325   /* Pop the length stack, too.  */
326   assert (obstack_object_size (&len_stack) >= sizeof (size_t));
327   obstack_blank (&len_stack, -(sizeof (size_t)));
328 }
329
330 /* Copy the SRC_LEN bytes of data beginning at SRC into the DST_LEN-byte
331    buffer, DST, so that the last source byte is at the end of the destination
332    buffer.  If SRC_LEN is longer than DST_LEN, then set *TRUNCATED to non-zero.
333    Set *RESULT to point to the beginning of (the portion of) the source data
334    in DST.  Return the number of bytes remaining in the destination buffer.  */
335
336 static size_t
337 right_justify (char *dst, size_t dst_len, const char *src, size_t src_len,
338                char **result, int *truncated)
339 {
340   const char *sp;
341   char *dp;
342
343   if (src_len <= dst_len)
344     {
345       sp = src;
346       dp = dst + (dst_len - src_len);
347       *truncated = 0;
348     }
349   else
350     {
351       sp = src + (src_len - dst_len);
352       dp = dst;
353       src_len = dst_len;
354       *truncated = 1;
355     }
356
357   memcpy (dp, sp, src_len);
358
359   *result = dp;
360   return dst_len - src_len;
361 }
362
363 /* Using the global directory name obstack, create the full path to FILENAME.
364    Return it in sometimes-realloc'd space that should not be freed by the
365    caller.  Realloc as necessary.  If realloc fails, use a static buffer
366    and put as long a suffix in that buffer as possible.  */
367
368 static char *
369 full_filename (const char *filename)
370 {
371   static char *buf = NULL;
372   static size_t n_allocated = 0;
373
374   int dir_len = obstack_object_size (&dir_stack);
375   char *dir_name = (char *) obstack_base (&dir_stack);
376   size_t n_bytes_needed;
377   size_t filename_len;
378
379   filename_len = strlen (filename);
380   n_bytes_needed = dir_len + filename_len + 1;
381
382   if (n_bytes_needed > n_allocated)
383     {
384       /* FIXME: use realloc, not xrealloc.  */
385       /* But be sure realloc accepts NULL first arg.
386          FIXME: replace with rpl_realloc if not.  */
387       /* This funciton can't use xrealloc.  Otherwise, out-of-memory
388          errors involving a file name to be expanded here wouldn't ever
389          be issued.  Use realloc and fall back on using a static buffer
390          if memory is a problem.  */
391       buf = xrealloc (buf, n_bytes_needed);
392       n_allocated = n_bytes_needed;
393
394       if (buf == NULL)
395         {
396 #define SBUF_SIZE 512
397 #define ELLIPSES_PREFIX "[...]"
398           static char static_buf[SBUF_SIZE];
399           int truncated;
400           size_t len;
401           char *p;
402
403           len = right_justify (static_buf, SBUF_SIZE, filename,
404                                filename_len + 1, &p, &truncated);
405           right_justify (static_buf, len, dir_name, dir_len, &p, &truncated);
406           if (truncated)
407             {
408               memcpy (static_buf, ELLIPSES_PREFIX,
409                       sizeof (ELLIPSES_PREFIX) - 1);
410             }
411           return p;
412         }
413     }
414
415   /* Copy directory part, including trailing slash.  */
416   memcpy (buf, dir_name, dir_len);
417
418   /* Append filename part, including trailing zero byte.  */
419   memcpy (buf + dir_len, filename, filename_len + 1);
420
421   assert (strlen (buf) + 1 == n_bytes_needed);
422
423   return buf;
424 }
425
426 static inline void
427 fspec_init_file (struct File_spec *fs, const char *filename)
428 {
429   fs->filename = (char *) filename;
430   fs->have_full_mode = 0;
431   fs->have_filetype_mode = 0;
432 }
433
434 static inline void
435 fspec_init_dp (struct File_spec *fs, struct dirent *dp)
436 {
437   fs->filename = dp->d_name;
438   fs->have_full_mode = 0;
439   fs->have_filetype_mode = 0;
440   fs->inum = D_INO (dp);
441
442 #if D_TYPE_IN_DIRENT && defined (DT_UNKNOWN)
443   if (filetype_mode != DT_UNKNOWN)
444     {
445       fs->have_filetype_mode = 1;
446       fs->mode = filetype_mode;
447     }
448 #endif
449 }
450
451 static inline int
452 fspec_get_full_mode (struct File_spec *fs, mode_t *full_mode)
453 {
454   struct stat stat_buf;
455
456   if (fs->have_full_mode)
457     {
458       *full_mode = fs->mode;
459       return 0;
460     }
461
462   if (lstat (fs->filename, &stat_buf))
463     return 1;
464
465   fs->have_full_mode = 1;
466   fs->have_filetype_mode = 1;
467   fs->mode = stat_buf.st_mode;
468   fs->inum = stat_buf.st_ino;
469
470   *full_mode = stat_buf.st_mode;
471   return 0;
472 }
473
474 static inline int
475 fspec_get_filetype_mode (struct File_spec *fs, mode_t *filetype_mode)
476 {
477   int fail;
478
479   if (fs->have_filetype_mode)
480     {
481       *filetype_mode = fs->mode;
482       fail = 0;
483     }
484   else
485     {
486       fail = fspec_get_full_mode (fs, filetype_mode);
487     }
488
489   return fail;
490 }
491
492 static inline mode_t
493 fspec_filetype_mode (const struct File_spec *fs)
494 {
495   assert (fs->have_filetype_mode);
496   return fs->mode;
497 }
498
499 /* Recursively remove all of the entries in the current directory.
500    Return an indication of the success of the operation.  */
501
502 enum RM_status
503 remove_cwd_entries (void)
504 {
505   /* NOTE: this is static.  */
506   static DIR *dirp = NULL;
507
508   /* NULL or a malloc'd and initialized hash table of entries in the
509      current directory that have been processed but not removed --
510      due either to an error or to an interactive `no' response.  */
511   struct hash_table *ht = NULL;
512
513   struct dirent *dp;
514   enum RM_status status = RM_OK;
515
516   if (dirp)
517     {
518       if (CLOSEDIR (dirp))
519         {
520           /* FIXME-someday: but this is actually the previously opened dir.  */
521           error (0, errno, "%s", full_filename ("."));
522           status = RM_ERROR;
523         }
524     }
525
526   do
527     {
528       errno = 0;
529       dirp = opendir (".");
530       if (dirp == NULL)
531         {
532           if (errno != ENOENT || !ignore_missing_files)
533             {
534               error (0, errno, "%s", full_filename ("."));
535               status = RM_ERROR;
536             }
537           break;
538         }
539
540       while ((dp = readdir (dirp)) != NULL)
541         {
542           /* Skip this entry if it's `.' or `..'.  */
543           if (DOT_OR_DOTDOT (dp->d_name))
544             continue;
545
546           /* Skip this entry if it's in the table of ones we've already
547              processed.  */
548           if (ht && hash_find_item (ht, (dp)->d_name))
549             continue;
550
551           {
552             struct File_spec fs;
553             enum RM_status tmp_status;
554             fspec_init_dp (&fs, dp);
555             tmp_status = rm (&fs, 0);
556             /* Update status.  */
557             if (tmp_status > status)
558               status = tmp_status;
559             assert (VALID_STATUS (status));
560           }
561
562           /* If this entry was not removed (due either to an error or to
563              an interactive `no' response), record it in the hash table so
564              we don't consider it again if we reopen this directory later.  */
565           if (status != RM_OK)
566             {
567               void *old_item;
568               char *p;
569               int fail;
570
571               if (ht == NULL)
572                 {
573                   ht = hash_init_table (NULL, HT_INITIAL_CAPACITY, 0, 0,
574                                         hash_string_1, hash_string_2,
575                                         hash_compare_strings);
576                   if (ht == NULL)
577                     error (1, 0, _("Memory exhausted"));
578                 }
579               p = xmalloc (NLENGTH (dp) + 1);
580               stpncpy (p, (dp)->d_name, NLENGTH (dp));
581               fail = hash_insert_item (ht, p, &old_item);
582               assert (old_item == NULL);
583               if (fail)
584                 error (1, 0, _("Memory exhausted"));
585             }
586
587           if (dirp == NULL)
588             break;
589         }
590     }
591   while (dirp == NULL);
592
593   if (CLOSEDIR (dirp))
594     {
595       error (0, errno, "%s", full_filename ("."));
596       status = 1;
597     }
598   dirp = NULL;
599
600   if (ht)
601     {
602       hash_free_items (ht);
603       hash_free_table (ht);
604       free (ht);
605     }
606
607   return status;
608 }
609
610 /* Query the user if appropriate, and if ok try to remove the
611    file or directory specified by FS.  Return RM_OK if it is removed,
612    and RM_ERROR or RM_USER_DECLINED if not.
613    FIXME: describe IS_DIR parameter.  */
614
615 static enum RM_status
616 remove_file (struct File_spec *fs)
617 {
618   int asked = 0;
619   char *pathname = fs->filename;
620
621   if (!ignore_missing_files && (interactive || stdin_tty)
622       && euidaccess (pathname, W_OK) )
623     {
624       if (!S_ISLNK (fspec_filetype_mode (fs)))
625         {
626           error (0, 0,
627                  (S_ISDIR (fspec_filetype_mode (fs))
628                   ? _("remove write-protected directory `%s'? ")
629                   : _("remove write-protected file `%s'? ")),
630                  full_filename (pathname));
631           if (!yesno ())
632             return RM_USER_DECLINED;
633
634           asked = 1;
635         }
636     }
637
638   if (!asked && interactive)
639     {
640       error (0, 0,
641              (S_ISDIR (fspec_filetype_mode (fs))
642               ? _("remove directory `%s'? ")
643               : _("remove `%s'? ")),
644              full_filename (pathname));
645       if (!yesno ())
646         return RM_USER_DECLINED;
647     }
648
649   if (verbose)
650     printf ("%s\n", full_filename (pathname));
651
652   if (unlink (pathname) && (errno != ENOENT || !ignore_missing_files))
653     {
654       error (0, errno, _("cannot unlink `%s'"), full_filename (pathname));
655       return RM_ERROR;
656     }
657   return RM_OK;
658 }
659
660 /* If not in recursive mode, print an error message and return RM_ERROR.
661    Otherwise, query the user if appropriate, then try to recursively
662    remove directory `pathname', which STATP contains info about.
663    Return 0 if `pathname' is removed, 1 if not.
664    FIXME: describe need_save_cwd parameter.  */
665
666 static enum RM_status
667 remove_dir (struct File_spec *fs, int need_save_cwd)
668 {
669   enum RM_status status;
670   struct saved_cwd cwd;
671   char *dir_name = fs->filename;
672   const char *fmt = NULL;
673
674   if (!recursive)
675     {
676       error (0, 0, _("%s: is a directory"), full_filename (dir_name));
677       return RM_ERROR;
678     }
679
680   if (!ignore_missing_files && (interactive || stdin_tty)
681       && euidaccess (dir_name, W_OK))
682     {
683       fmt = _("directory `%s' is write protected; descend into it anyway? ");
684     }
685   else if (interactive)
686     {
687       fmt = _("descend into directory `%s'? ");
688     }
689
690   if (fmt)
691     {
692       error (0, 0, fmt, full_filename (dir_name));
693       if (!yesno ())
694         return RM_USER_DECLINED;
695     }
696
697   if (verbose)
698     printf ("%s\n", full_filename (dir_name));
699
700   /* Save cwd if needed.  */
701   if (need_save_cwd && save_cwd (&cwd))
702     return RM_ERROR;
703
704   /* Make target directory the current one.  */
705   if (chdir (dir_name) < 0)
706     {
707       error (0, errno, _("cannot change to directory %s"),
708              full_filename (dir_name));
709       if (need_save_cwd)
710         free_cwd (&cwd);
711       return RM_ERROR;
712     }
713
714   push_dir (dir_name);
715
716   /* Save a copy of dir_name.  Otherwise, remove_cwd_entries may clobber
717      dir_name because dir_name is just a pointer to the dir entry's d_name
718      field, and remove_cwd_entries may close the directory.  */
719   ASSIGN_STRDUPA (dir_name, dir_name);
720
721   status = remove_cwd_entries ();
722
723   pop_dir ();
724
725   /* Restore cwd.  */
726   if (need_save_cwd)
727     {
728       if (restore_cwd (&cwd, NULL, NULL))
729         {
730           free_cwd (&cwd);
731           return RM_ERROR;
732         }
733       free_cwd (&cwd);
734     }
735   else if (chdir ("..") < 0)
736     {
737       error (0, errno, _("cannot change back to directory %s via `..'"),
738              full_filename (dir_name));
739       return RM_ERROR;
740     }
741
742   if (interactive)
743     {
744       error (0, 0, _("remove directory `%s'%s? "), full_filename (dir_name),
745              (status != RM_OK ? _(" (might be nonempty)") : ""));
746       if (!yesno ())
747         {
748           return RM_USER_DECLINED;
749         }
750     }
751
752   if (rmdir (dir_name) && (errno != ENOENT || !ignore_missing_files))
753     {
754       error (0, errno, _("cannot remove directory `%s'"),
755              full_filename (dir_name));
756       return RM_ERROR;
757     }
758
759   return RM_OK;
760 }
761
762 /* Remove the file or directory specified by FS after checking appropriate
763    things.  Return RM_OK if it is removed, and RM_ERROR or RM_USER_DECLINED
764    if not.  If USER_SPECIFIED_NAME is non-zero, then the name part of FS may
765    be `.', `..', or may contain slashes.  Otherwise, it must be a simple file
766    name (and hence must specify a file in the current directory).  */
767
768 static enum RM_status
769 rm (struct File_spec *fs, int user_specified_name)
770 {
771   mode_t filetype_mode;
772
773   if (user_specified_name)
774     {
775       char *base = base_name (fs->filename);
776
777       if (DOT_OR_DOTDOT (base))
778         {
779           error (0, 0, _("cannot remove `.' or `..'"));
780           return RM_ERROR;
781         }
782     }
783
784   if (fspec_get_filetype_mode (fs, &filetype_mode))
785     {
786       if (ignore_missing_files && errno == ENOENT)
787         return RM_OK;
788
789       error (0, errno, _("cannot remove `%s'"), full_filename (fs->filename));
790       return RM_ERROR;
791     }
792
793   if (S_ISDIR (filetype_mode))
794     {
795       int fail;
796       struct active_dir_ent *old_ent;
797
798       /* Insert this directory in the active_dir_map.
799          If there is already a directory in the map with the same inum,
800          then there's *probably* a directory cycle.  This test can get
801          a false positive if two directories have the same inode number
802          but different device numbers and one directory contains the
803          other.  But since people don't often try to delete hierarchies
804          containing mount points, and when they do, duplicate inode
805          numbers are not that likely, this isn't worth detecting.  */
806       fail = hash_insert_item (active_dir_map,
807                                make_active_dir_ent (fs->inum, current_depth ()),
808                                (void **) &old_ent);
809       if (fail)
810         error (1, 0, _("Memory exhausted"));
811
812       if (old_ent)
813         {
814           error (0, 0, _("\
815 WARNING: Circular directory structure.\n\
816 This almost certainly means that you have a corrupted file system.\n\
817 NOTIFY YOUR SYSTEM MANAGER.\n\
818 The following two directories have the same inode number:\n"));
819           print_nth_dir (stderr, current_depth ());
820           fputc ('\n', stderr);
821           print_nth_dir (stderr, old_ent->depth);
822           fputc ('\n', stderr);
823           fflush (stderr);
824
825           free (old_ent);
826
827           if (interactive)
828             {
829               error (0, 0, _("continue? "));
830               if (yesno ())
831                 return RM_ERROR;
832             }
833           exit (1);
834         }
835     }
836
837   if (!S_ISDIR (filetype_mode) || unlink_dirs)
838     {
839       return remove_file (fs);
840     }
841   else
842     {
843       int need_save_cwd = user_specified_name;
844       enum RM_status status;
845       struct active_dir_ent tmp;
846       struct active_dir_ent *old_ent;
847
848       if (need_save_cwd)
849         need_save_cwd = (strchr (fs->filename, '/') != NULL);
850
851       status = remove_dir (fs, need_save_cwd);
852
853       /* Remove this directory from the active_dir_map.  */
854       tmp.inum = fs->inum;
855       hash_delete_item (active_dir_map, &tmp, (void **) &old_ent);
856       assert (old_ent != NULL);
857       free (old_ent);
858
859       return status;
860     }
861 }
862
863 int
864 main (int argc, char **argv)
865 {
866   int fail = 0;
867   int c;
868
869   program_name = argv[0];
870   setlocale (LC_ALL, "");
871   bindtextdomain (PACKAGE, LOCALEDIR);
872   textdomain (PACKAGE);
873
874   verbose = ignore_missing_files = recursive = interactive
875     = unlink_dirs = 0;
876
877   while ((c = getopt_long (argc, argv, "dfirvR", long_opts, NULL)) != -1)
878     {
879       switch (c)
880         {
881         case 0:         /* Long option.  */
882           break;
883         case 'd':
884           unlink_dirs = 1;
885           break;
886         case 'f':
887           interactive = 0;
888           ignore_missing_files = 1;
889           break;
890         case 'i':
891           interactive = 1;
892           ignore_missing_files = 0;
893           break;
894         case 'r':
895         case 'R':
896           recursive = 1;
897           break;
898         case 'v':
899           verbose = 1;
900           break;
901         default:
902           usage (1);
903         }
904     }
905
906   if (show_version)
907     {
908       printf ("rm (%s) %s\n", GNU_PACKAGE, VERSION);
909       exit (0);
910     }
911
912   if (show_help)
913     usage (0);
914
915   if (optind == argc)
916     {
917       if (ignore_missing_files)
918         exit (0);
919       else
920         {
921           error (0, 0, _("too few arguments"));
922           usage (1);
923         }
924     }
925
926   stdin_tty = isatty (STDIN_FILENO);
927
928   /* Initialize dir-stack obstacks.  */
929   obstack_init (&dir_stack);
930   obstack_init (&len_stack);
931
932   active_dir_map = hash_init_table (NULL, ACTIVE_DIR_INITIAL_CAPACITY, 0, 0,
933                                     hash_active_dir_ent_1,
934                                     hash_active_dir_ent_2,
935                                     hash_compare_active_dir_ents);
936
937   for (; optind < argc; optind++)
938     {
939       struct File_spec fs;
940       enum RM_status status;
941
942       /* Stripping slashes is harmless for rmdir;
943          if the arg is not a directory, it will fail with ENOTDIR.  */
944       strip_trailing_slashes (argv[optind]);
945       fspec_init_file (&fs, argv[optind]);
946       status = rm (&fs, 1);
947       assert (VALID_STATUS (status));
948       if (status == RM_ERROR)
949         fail = 1;
950     }
951
952   exit (fail);
953 }