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