Use memcpy, not stpncpy. This fixes a UMR.
[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 "oa-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 hash_table *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 long
250 hash_active_dir_ent_1 (void const *x)
251 {
252   struct active_dir_ent const *ade = x;
253   return_INTEGER_HASH_1 (ade->inum);
254 }
255
256 static unsigned long
257 hash_active_dir_ent_2 (void const *x)
258 {
259   struct active_dir_ent const *ade = x;
260   return_INTEGER_HASH_2 (ade->inum);
261 }
262
263 static int
264 hash_compare_active_dir_ents (void const *x, void const *y)
265 {
266   struct active_dir_ent const *a = x;
267   struct active_dir_ent const *b = y;
268   return_INTEGER_COMPARE (a->inum, b->inum);
269 }
270
271 static unsigned long
272 hash_string_1 (void const *x)
273 {
274   return_STRING_HASH_1 (x);
275 }
276
277 static unsigned long
278 hash_string_2 (void const *x)
279 {
280   return_STRING_HASH_2 (x);
281 }
282
283 static int
284 hash_compare_strings (void const *x, void const *y)
285 {
286   return strcmp (x, y);
287 }
288
289 static void
290 usage (int status)
291 {
292   if (status != 0)
293     fprintf (stderr, _("Try `%s --help' for more information.\n"),
294              program_name);
295   else
296     {
297       printf (_("Usage: %s [OPTION]... FILE...\n"), program_name);
298       printf (_("\
299 Remove (unlink) the FILE(s).\n\
300 \n\
301   -d, --directory       unlink directory, even if non-empty (super-user only)\n\
302   -f, --force           ignore nonexistent files, never prompt\n\
303   -i, --interactive     prompt before any removal\n\
304   -r, -R, --recursive   remove the contents of directories recursively\n\
305   -v, --verbose         explain what is being done\n\
306       --help            display this help and exit\n\
307       --version         output version information and exit\n\
308 "));
309       puts (_("\nReport bugs to <fileutils-bugs@gnu.ai.mit.edu>."));
310     }
311   exit (status);
312 }
313
314 static inline void
315 push_dir (const char *dir_name)
316 {
317   size_t len;
318
319   len = strlen (dir_name);
320
321   /* Append the string onto the stack.  */
322   obstack_grow (&dir_stack, dir_name, len);
323
324   /* Append a trailing slash.  */
325   obstack_1grow (&dir_stack, '/');
326
327   /* Add one for the slash.  */
328   ++len;
329
330   /* Push the length (including slash) onto its stack.  */
331   obstack_grow (&len_stack, &len, sizeof (len));
332 }
333
334 static inline void
335 pop_dir (void)
336 {
337   int n_lengths = obstack_object_size (&len_stack) / sizeof (size_t);
338   size_t *length = (size_t *) obstack_base (&len_stack);
339   size_t top_len;
340
341   assert (n_lengths > 0);
342   top_len = length[n_lengths - 1];
343   assert (top_len >= 2);
344
345   /* Pop off the specified length of pathname.  */
346   assert (obstack_object_size (&dir_stack) >= top_len);
347   obstack_blank (&dir_stack, -top_len);
348
349   /* Pop the length stack, too.  */
350   assert (obstack_object_size (&len_stack) >= sizeof (size_t));
351   obstack_blank (&len_stack, -(sizeof (size_t)));
352 }
353
354 /* Copy the SRC_LEN bytes of data beginning at SRC into the DST_LEN-byte
355    buffer, DST, so that the last source byte is at the end of the destination
356    buffer.  If SRC_LEN is longer than DST_LEN, then set *TRUNCATED to non-zero.
357    Set *RESULT to point to the beginning of (the portion of) the source data
358    in DST.  Return the number of bytes remaining in the destination buffer.  */
359
360 static size_t
361 right_justify (char *dst, size_t dst_len, const char *src, size_t src_len,
362                char **result, int *truncated)
363 {
364   const char *sp;
365   char *dp;
366
367   if (src_len <= dst_len)
368     {
369       sp = src;
370       dp = dst + (dst_len - src_len);
371       *truncated = 0;
372     }
373   else
374     {
375       sp = src + (src_len - dst_len);
376       dp = dst;
377       src_len = dst_len;
378       *truncated = 1;
379     }
380
381   memcpy (dp, sp, src_len);
382
383   *result = dp;
384   return dst_len - src_len;
385 }
386
387 /* Using the global directory name obstack, create the full path to FILENAME.
388    Return it in sometimes-realloc'd space that should not be freed by the
389    caller.  Realloc as necessary.  If realloc fails, use a static buffer
390    and put as long a suffix in that buffer as possible.  */
391
392 static char *
393 full_filename (const char *filename)
394 {
395   static char *buf = NULL;
396   static size_t n_allocated = 0;
397
398   int dir_len = obstack_object_size (&dir_stack);
399   char *dir_name = (char *) obstack_base (&dir_stack);
400   size_t n_bytes_needed;
401   size_t filename_len;
402
403   filename_len = strlen (filename);
404   n_bytes_needed = dir_len + filename_len + 1;
405
406   if (n_bytes_needed > n_allocated)
407     {
408       /* This code requires that realloc accept NULL as the first arg.
409          This function must not use xrealloc.  Otherwise, an out-of-memory
410          error involving a file name to be expanded here wouldn't ever
411          be issued.  Use realloc and fall back on using a static buffer
412          if memory allocation fails.  */
413       buf = realloc (buf, n_bytes_needed);
414       n_allocated = n_bytes_needed;
415
416       if (buf == NULL)
417         {
418 #define SBUF_SIZE 512
419 #define ELLIPSES_PREFIX "[...]"
420           static char static_buf[SBUF_SIZE];
421           int truncated;
422           size_t len;
423           char *p;
424
425           len = right_justify (static_buf, SBUF_SIZE, filename,
426                                filename_len + 1, &p, &truncated);
427           right_justify (static_buf, len, dir_name, dir_len, &p, &truncated);
428           if (truncated)
429             {
430               memcpy (static_buf, ELLIPSES_PREFIX,
431                       sizeof (ELLIPSES_PREFIX) - 1);
432             }
433           return p;
434         }
435     }
436
437   /* Copy directory part, including trailing slash.  */
438   memcpy (buf, dir_name, dir_len);
439
440   /* Append filename part, including trailing zero byte.  */
441   memcpy (buf + dir_len, filename, filename_len + 1);
442
443   assert (strlen (buf) + 1 == n_bytes_needed);
444
445   return buf;
446 }
447
448 static inline void
449 fspec_init_file (struct File_spec *fs, const char *filename)
450 {
451   fs->filename = (char *) filename;
452   fs->have_full_mode = 0;
453   fs->have_filetype_mode = 0;
454 }
455
456 static inline void
457 fspec_init_dp (struct File_spec *fs, struct dirent *dp)
458 {
459   fs->filename = dp->d_name;
460   fs->have_full_mode = 0;
461   fs->have_filetype_mode = 0;
462   fs->inum = D_INO (dp);
463
464 #if D_TYPE_IN_DIRENT && defined (DT_UNKNOWN)
465   if (filetype_mode != DT_UNKNOWN)
466     {
467       fs->have_filetype_mode = 1;
468       fs->mode = filetype_mode;
469     }
470 #endif
471 }
472
473 static inline int
474 fspec_get_full_mode (struct File_spec *fs, mode_t *full_mode)
475 {
476   struct stat stat_buf;
477
478   if (fs->have_full_mode)
479     {
480       *full_mode = fs->mode;
481       return 0;
482     }
483
484   if (lstat (fs->filename, &stat_buf))
485     return 1;
486
487   fs->have_full_mode = 1;
488   fs->have_filetype_mode = 1;
489   fs->mode = stat_buf.st_mode;
490   fs->inum = stat_buf.st_ino;
491
492   *full_mode = stat_buf.st_mode;
493   return 0;
494 }
495
496 static inline int
497 fspec_get_filetype_mode (struct File_spec *fs, mode_t *filetype_mode)
498 {
499   int fail;
500
501   if (fs->have_filetype_mode)
502     {
503       *filetype_mode = fs->mode;
504       fail = 0;
505     }
506   else
507     {
508       fail = fspec_get_full_mode (fs, filetype_mode);
509     }
510
511   return fail;
512 }
513
514 static inline mode_t
515 fspec_filetype_mode (const struct File_spec *fs)
516 {
517   assert (fs->have_filetype_mode);
518   return fs->mode;
519 }
520
521 /* Recursively remove all of the entries in the current directory.
522    Return an indication of the success of the operation.  */
523
524 enum RM_status
525 remove_cwd_entries (void)
526 {
527   /* NOTE: this is static.  */
528   static DIR *dirp = NULL;
529
530   /* NULL or a malloc'd and initialized hash table of entries in the
531      current directory that have been processed but not removed --
532      due either to an error or to an interactive `no' response.  */
533   struct hash_table *ht = NULL;
534
535   struct dirent *dp;
536   enum RM_status status = RM_OK;
537
538   if (dirp)
539     {
540       if (CLOSEDIR (dirp))
541         {
542           /* FIXME-someday: but this is actually the previously opened dir.  */
543           error (0, errno, "%s", full_filename ("."));
544           status = RM_ERROR;
545         }
546     }
547
548   do
549     {
550       errno = 0;
551       dirp = opendir (".");
552       if (dirp == NULL)
553         {
554           if (errno != ENOENT || !ignore_missing_files)
555             {
556               error (0, errno, "%s", full_filename ("."));
557               status = RM_ERROR;
558             }
559           break;
560         }
561
562       while ((dp = readdir (dirp)) != NULL)
563         {
564           /* Skip this entry if it's `.' or `..'.  */
565           if (DOT_OR_DOTDOT (dp->d_name))
566             continue;
567
568           /* Skip this entry if it's in the table of ones we've already
569              processed.  */
570           if (ht && hash_find_item (ht, (dp)->d_name))
571             continue;
572
573           {
574             struct File_spec fs;
575             enum RM_status tmp_status;
576             fspec_init_dp (&fs, dp);
577             tmp_status = rm (&fs, 0);
578             /* Update status.  */
579             if (tmp_status > status)
580               status = tmp_status;
581             assert (VALID_STATUS (status));
582           }
583
584           /* If this entry was not removed (due either to an error or to
585              an interactive `no' response), record it in the hash table so
586              we don't consider it again if we reopen this directory later.  */
587           if (status != RM_OK)
588             {
589               void *old_item;
590               char *p;
591               int fail;
592
593               if (ht == NULL)
594                 {
595                   ht = hash_init_table (NULL, HT_INITIAL_CAPACITY, 0, 0,
596                                         hash_string_1, hash_string_2,
597                                         hash_compare_strings);
598                   if (ht == NULL)
599                     error (1, 0, _("Memory exhausted"));
600                 }
601               p = xmalloc (NLENGTH (dp) + 1);
602               memcpy (p, (dp)->d_name, NLENGTH (dp) + 1);
603               fail = hash_insert_item (ht, p, &old_item);
604               assert (old_item == NULL);
605               if (fail)
606                 error (1, 0, _("Memory exhausted"));
607             }
608
609           if (dirp == NULL)
610             break;
611         }
612     }
613   while (dirp == NULL);
614
615   if (CLOSEDIR (dirp))
616     {
617       error (0, errno, "%s", full_filename ("."));
618       status = 1;
619     }
620   dirp = NULL;
621
622   if (ht)
623     {
624       hash_free_items (ht);
625       hash_free_table (ht);
626       free (ht);
627     }
628
629   return status;
630 }
631
632 /* Query the user if appropriate, and if ok try to remove the
633    file or directory specified by FS.  Return RM_OK if it is removed,
634    and RM_ERROR or RM_USER_DECLINED if not.  */
635
636 static enum RM_status
637 remove_file (struct File_spec *fs)
638 {
639   int asked = 0;
640   char *pathname = fs->filename;
641
642   if (!ignore_missing_files && (interactive || stdin_tty)
643       && euidaccess (pathname, W_OK) )
644     {
645       if (!S_ISLNK (fspec_filetype_mode (fs)))
646         {
647           error (0, 0,
648                  (S_ISDIR (fspec_filetype_mode (fs))
649                   ? _("remove write-protected directory `%s'? ")
650                   : _("remove write-protected file `%s'? ")),
651                  full_filename (pathname));
652           if (!yesno ())
653             return RM_USER_DECLINED;
654
655           asked = 1;
656         }
657     }
658
659   if (!asked && interactive)
660     {
661       error (0, 0,
662              (S_ISDIR (fspec_filetype_mode (fs))
663               ? _("remove directory `%s'? ")
664               : _("remove `%s'? ")),
665              full_filename (pathname));
666       if (!yesno ())
667         return RM_USER_DECLINED;
668     }
669
670   if (verbose)
671     printf ("%s\n", full_filename (pathname));
672
673   if (unlink (pathname) && (errno != ENOENT || !ignore_missing_files))
674     {
675       error (0, errno, _("cannot unlink `%s'"), full_filename (pathname));
676       return RM_ERROR;
677     }
678   return RM_OK;
679 }
680
681 /* If not in recursive mode, print an error message and return RM_ERROR.
682    Otherwise, query the user if appropriate, then try to recursively
683    remove directory `pathname', which STATP contains info about.
684    Return 0 if `pathname' is removed, 1 if not.
685    FIXME: describe need_save_cwd parameter.  */
686
687 static enum RM_status
688 remove_dir (struct File_spec *fs, int need_save_cwd)
689 {
690   enum RM_status status;
691   struct saved_cwd cwd;
692   char *dir_name = fs->filename;
693   const char *fmt = NULL;
694
695   if (!recursive)
696     {
697       error (0, 0, _("%s: is a directory"), full_filename (dir_name));
698       return RM_ERROR;
699     }
700
701   if (!ignore_missing_files && (interactive || stdin_tty)
702       && euidaccess (dir_name, W_OK))
703     {
704       fmt = _("directory `%s' is write protected; descend into it anyway? ");
705     }
706   else if (interactive)
707     {
708       fmt = _("descend into directory `%s'? ");
709     }
710
711   if (fmt)
712     {
713       error (0, 0, fmt, full_filename (dir_name));
714       if (!yesno ())
715         return RM_USER_DECLINED;
716     }
717
718   if (verbose)
719     printf ("%s\n", full_filename (dir_name));
720
721   /* Save cwd if needed.  */
722   if (need_save_cwd && save_cwd (&cwd))
723     return RM_ERROR;
724
725   /* Make target directory the current one.  */
726   if (chdir (dir_name) < 0)
727     {
728       error (0, errno, _("cannot change to directory %s"),
729              full_filename (dir_name));
730       if (need_save_cwd)
731         free_cwd (&cwd);
732       return RM_ERROR;
733     }
734
735   push_dir (dir_name);
736
737   /* Save a copy of dir_name.  Otherwise, remove_cwd_entries may clobber
738      dir_name because dir_name is just a pointer to the dir entry's d_name
739      field, and remove_cwd_entries may close the directory.  */
740   ASSIGN_STRDUPA (dir_name, dir_name);
741
742   status = remove_cwd_entries ();
743
744   pop_dir ();
745
746   /* Restore cwd.  */
747   if (need_save_cwd)
748     {
749       if (restore_cwd (&cwd, NULL, NULL))
750         {
751           free_cwd (&cwd);
752           return RM_ERROR;
753         }
754       free_cwd (&cwd);
755     }
756   else if (chdir ("..") < 0)
757     {
758       error (0, errno, _("cannot change back to directory %s via `..'"),
759              full_filename (dir_name));
760       return RM_ERROR;
761     }
762
763   if (interactive)
764     {
765       error (0, 0, _("remove directory `%s'%s? "), full_filename (dir_name),
766              (status != RM_OK ? _(" (might be nonempty)") : ""));
767       if (!yesno ())
768         {
769           return RM_USER_DECLINED;
770         }
771     }
772
773   if (rmdir (dir_name) && (errno != ENOENT || !ignore_missing_files))
774     {
775       error (0, errno, _("cannot remove directory `%s'"),
776              full_filename (dir_name));
777       return RM_ERROR;
778     }
779
780   return RM_OK;
781 }
782
783 /* Remove the file or directory specified by FS after checking appropriate
784    things.  Return RM_OK if it is removed, and RM_ERROR or RM_USER_DECLINED
785    if not.  If USER_SPECIFIED_NAME is non-zero, then the name part of FS may
786    be `.', `..', or may contain slashes.  Otherwise, it must be a simple file
787    name (and hence must specify a file in the current directory).  */
788
789 static enum RM_status
790 rm (struct File_spec *fs, int user_specified_name)
791 {
792   mode_t filetype_mode;
793
794   if (user_specified_name)
795     {
796       char *base = base_name (fs->filename);
797
798       if (DOT_OR_DOTDOT (base))
799         {
800           error (0, 0, _("cannot remove `.' or `..'"));
801           return RM_ERROR;
802         }
803     }
804
805   if (fspec_get_filetype_mode (fs, &filetype_mode))
806     {
807       if (ignore_missing_files && errno == ENOENT)
808         return RM_OK;
809
810       error (0, errno, _("cannot remove `%s'"), full_filename (fs->filename));
811       return RM_ERROR;
812     }
813
814   if (S_ISDIR (filetype_mode))
815     {
816       int fail;
817       struct active_dir_ent *old_ent;
818
819       /* Insert this directory in the active_dir_map.
820          If there is already a directory in the map with the same inum,
821          then there's *probably* a directory cycle.  This test can get
822          a false positive if two directories have the same inode number
823          but different device numbers and one directory contains the
824          other.  But since people don't often try to delete hierarchies
825          containing mount points, and when they do, duplicate inode
826          numbers are not that likely, this isn't worth detecting.  */
827       fail = hash_insert_item (active_dir_map,
828                                make_active_dir_ent (fs->inum, current_depth ()),
829                                (void **) &old_ent);
830       if (fail)
831         error (1, 0, _("Memory exhausted"));
832
833       if (old_ent)
834         {
835           error (0, 0, _("\
836 WARNING: Circular directory structure.\n\
837 This almost certainly means that you have a corrupted file system.\n\
838 NOTIFY YOUR SYSTEM MANAGER.\n\
839 The following two directories have the same inode number:\n"));
840           print_nth_dir (stderr, current_depth ());
841           fputc ('\n', stderr);
842           print_nth_dir (stderr, old_ent->depth);
843           fputc ('\n', stderr);
844           fflush (stderr);
845
846           free (old_ent);
847
848           if (interactive)
849             {
850               error (0, 0, _("continue? "));
851               if (yesno ())
852                 return RM_ERROR;
853             }
854           exit (1);
855         }
856     }
857
858   if (!S_ISDIR (filetype_mode) || unlink_dirs)
859     {
860       return remove_file (fs);
861     }
862   else
863     {
864       int need_save_cwd = user_specified_name;
865       enum RM_status status;
866       struct active_dir_ent tmp;
867       struct active_dir_ent *old_ent;
868
869       if (need_save_cwd)
870         need_save_cwd = (strchr (fs->filename, '/') != NULL);
871
872       status = remove_dir (fs, need_save_cwd);
873
874       /* Remove this directory from the active_dir_map.  */
875       tmp.inum = fs->inum;
876       hash_delete_item (active_dir_map, &tmp, (void **) &old_ent);
877       assert (old_ent != NULL);
878       free (old_ent);
879
880       return status;
881     }
882 }
883
884 int
885 main (int argc, char **argv)
886 {
887   int fail = 0;
888   int c;
889
890   program_name = argv[0];
891   setlocale (LC_ALL, "");
892   bindtextdomain (PACKAGE, LOCALEDIR);
893   textdomain (PACKAGE);
894
895   verbose = ignore_missing_files = recursive = interactive
896     = unlink_dirs = 0;
897
898   while ((c = getopt_long (argc, argv, "dfirvR", long_opts, NULL)) != -1)
899     {
900       switch (c)
901         {
902         case 0:         /* Long option.  */
903           break;
904         case 'd':
905           unlink_dirs = 1;
906           break;
907         case 'f':
908           interactive = 0;
909           ignore_missing_files = 1;
910           break;
911         case 'i':
912           interactive = 1;
913           ignore_missing_files = 0;
914           break;
915         case 'r':
916         case 'R':
917           recursive = 1;
918           break;
919         case 'v':
920           verbose = 1;
921           break;
922         default:
923           usage (1);
924         }
925     }
926
927   if (show_version)
928     {
929       printf ("rm (%s) %s\n", GNU_PACKAGE, VERSION);
930       exit (0);
931     }
932
933   if (show_help)
934     usage (0);
935
936   if (optind == argc)
937     {
938       if (ignore_missing_files)
939         exit (0);
940       else
941         {
942           error (0, 0, _("too few arguments"));
943           usage (1);
944         }
945     }
946
947   stdin_tty = isatty (STDIN_FILENO);
948
949   /* Initialize dir-stack obstacks.  */
950   obstack_init (&dir_stack);
951   obstack_init (&len_stack);
952
953   active_dir_map = hash_init_table (NULL, ACTIVE_DIR_INITIAL_CAPACITY, 0, 0,
954                                     hash_active_dir_ent_1,
955                                     hash_active_dir_ent_2,
956                                     hash_compare_active_dir_ents);
957
958   for (; optind < argc; optind++)
959     {
960       struct File_spec fs;
961       enum RM_status status;
962
963       /* Stripping slashes is harmless for rmdir;
964          if the arg is not a directory, it will fail with ENOTDIR.  */
965       strip_trailing_slashes (argv[optind]);
966       fspec_init_file (&fs, argv[optind]);
967       status = rm (&fs, 1);
968       assert (VALID_STATUS (status));
969       if (status == RM_ERROR)
970         fail = 1;
971     }
972
973   exit (fail);
974 }