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