TODO: add an item for a chmod optimization
[platform/upstream/coreutils.git] / src / rmdir.c
1 /* rmdir -- remove directories
2
3    Copyright (C) 90, 91, 1995-2002, 2004-2008 Free Software
4    Foundation, Inc.
5
6    This program is free software: you can redistribute it and/or modify
7    it under the terms of the GNU General Public License as published by
8    the Free Software Foundation, either version 3 of the License, or
9    (at your option) any later version.
10
11    This program is distributed in the hope that it will be useful,
12    but WITHOUT ANY WARRANTY; without even the implied warranty of
13    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14    GNU General Public License for more details.
15
16    You should have received a copy of the GNU General Public License
17    along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
18
19 /* Options:
20    -p, --parent         Remove any parent dirs that are explicitly mentioned
21                         in an argument, if they become empty after the
22                         argument file is removed.
23
24    David MacKenzie <djm@ai.mit.edu>  */
25
26 #include <config.h>
27 #include <stdio.h>
28 #include <getopt.h>
29 #include <sys/types.h>
30
31 #include "system.h"
32 #include "error.h"
33 #include "prog-fprintf.h"
34 #include "quote.h"
35
36 /* The official name of this program (e.g., no `g' prefix).  */
37 #define PROGRAM_NAME "rmdir"
38
39 #define AUTHORS proper_name ("David MacKenzie")
40
41 /* If true, remove empty parent directories.  */
42 static bool remove_empty_parents;
43
44 /* If true, don't treat failure to remove a nonempty directory
45    as an error.  */
46 static bool ignore_fail_on_non_empty;
47
48 /* If true, output a diagnostic for every directory processed.  */
49 static bool verbose;
50
51 /* For long options that have no equivalent short option, use a
52    non-character as a pseudo short option, starting with CHAR_MAX + 1.  */
53 enum
54 {
55   IGNORE_FAIL_ON_NON_EMPTY_OPTION = CHAR_MAX + 1
56 };
57
58 static struct option const longopts[] =
59 {
60   /* Don't name this `--force' because it's not close enough in meaning
61      to e.g. rm's -f option.  */
62   {"ignore-fail-on-non-empty", no_argument, NULL,
63    IGNORE_FAIL_ON_NON_EMPTY_OPTION},
64
65   {"path", no_argument, NULL, 'p'},  /* Deprecated.  */
66   {"parents", no_argument, NULL, 'p'},
67   {"verbose", no_argument, NULL, 'v'},
68   {GETOPT_HELP_OPTION_DECL},
69   {GETOPT_VERSION_OPTION_DECL},
70   {NULL, 0, NULL, 0}
71 };
72
73 /* Return true if ERROR_NUMBER is one of the values associated
74    with a failed rmdir due to non-empty target directory.  */
75 static bool
76 errno_rmdir_non_empty (int error_number)
77 {
78   return (error_number == RMDIR_ERRNO_NOT_EMPTY);
79 }
80
81 /* Return true if when rmdir fails with errno == ERROR_NUMBER
82    the directory may be empty.  */
83 static bool
84 errno_may_be_empty (int error_number)
85 {
86   switch (error_number)
87     {
88     case EACCES:
89     case EPERM:
90     case EROFS:
91     case EEXIST:
92     case EBUSY:
93       return true;
94     default:
95       return false;
96     }
97 }
98
99 /* Return true if an rmdir failure with errno == error_number
100    for DIR is ignorable.  */
101 static bool
102 ignorable_failure (int error_number, char const *dir)
103 {
104   return (ignore_fail_on_non_empty
105           && (errno_rmdir_non_empty (error_number)
106               || (errno_may_be_empty (error_number)
107                   && is_empty_dir (AT_FDCWD, dir))));
108 }
109
110 /* Remove any empty parent directories of DIR.
111    If DIR contains slash characters, at least one of them
112    (beginning with the rightmost) is replaced with a NUL byte.
113    Return true if successful.  */
114
115 static bool
116 remove_parents (char *dir)
117 {
118   char *slash;
119   bool ok = true;
120
121   strip_trailing_slashes (dir);
122   while (1)
123     {
124       slash = strrchr (dir, '/');
125       if (slash == NULL)
126         break;
127       /* Remove any characters after the slash, skipping any extra
128          slashes in a row. */
129       while (slash > dir && *slash == '/')
130         --slash;
131       slash[1] = 0;
132
133       /* Give a diagnostic for each attempted removal if --verbose.  */
134       if (verbose)
135         prog_fprintf (stdout, _("removing directory, %s"), quote (dir));
136
137       ok = (rmdir (dir) == 0);
138
139       if (!ok)
140         {
141           /* Stop quietly if --ignore-fail-on-non-empty. */
142           if (ignorable_failure (errno, dir))
143             {
144               ok = true;
145             }
146           else
147             {
148               /* Barring race conditions, DIR is expected to be a directory.  */
149               error (0, errno, _("failed to remove directory %s"),
150                      quote (dir));
151             }
152           break;
153         }
154     }
155   return ok;
156 }
157
158 void
159 usage (int status)
160 {
161   if (status != EXIT_SUCCESS)
162     fprintf (stderr, _("Try `%s --help' for more information.\n"),
163              program_name);
164   else
165     {
166       printf (_("Usage: %s [OPTION]... DIRECTORY...\n"), program_name);
167       fputs (_("\
168 Remove the DIRECTORY(ies), if they are empty.\n\
169 \n\
170       --ignore-fail-on-non-empty\n\
171                   ignore each failure that is solely because a directory\n\
172                   is non-empty\n\
173 "), stdout);
174       fputs (_("\
175   -p, --parents   Remove DIRECTORY and its ancestors.  E.g., `rmdir -p a/b/c' is\n\
176                   similar to `rmdir a/b/c a/b a'.\n\
177   -v, --verbose   output a diagnostic for every directory processed\n\
178 "), stdout);
179       fputs (HELP_OPTION_DESCRIPTION, stdout);
180       fputs (VERSION_OPTION_DESCRIPTION, stdout);
181       emit_bug_reporting_address ();
182     }
183   exit (status);
184 }
185
186 int
187 main (int argc, char **argv)
188 {
189   bool ok = true;
190   int optc;
191
192   initialize_main (&argc, &argv);
193   set_program_name (argv[0]);
194   setlocale (LC_ALL, "");
195   bindtextdomain (PACKAGE, LOCALEDIR);
196   textdomain (PACKAGE);
197
198   atexit (close_stdout);
199
200   remove_empty_parents = false;
201
202   while ((optc = getopt_long (argc, argv, "pv", longopts, NULL)) != -1)
203     {
204       switch (optc)
205         {
206         case 'p':
207           remove_empty_parents = true;
208           break;
209         case IGNORE_FAIL_ON_NON_EMPTY_OPTION:
210           ignore_fail_on_non_empty = true;
211           break;
212         case 'v':
213           verbose = true;
214           break;
215         case_GETOPT_HELP_CHAR;
216         case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
217         default:
218           usage (EXIT_FAILURE);
219         }
220     }
221
222   if (optind == argc)
223     {
224       error (0, 0, _("missing operand"));
225       usage (EXIT_FAILURE);
226     }
227
228   for (; optind < argc; ++optind)
229     {
230       char *dir = argv[optind];
231
232       /* Give a diagnostic for each attempted removal if --verbose.  */
233       if (verbose)
234         prog_fprintf (stdout, _("removing directory, %s"), quote (dir));
235
236       if (rmdir (dir) != 0)
237         {
238           if (ignorable_failure (errno, dir))
239             continue;
240
241           /* Here, the diagnostic is less precise, since we have no idea
242              whether DIR is a directory.  */
243           error (0, errno, _("failed to remove %s"), quote (dir));
244           ok = false;
245         }
246       else if (remove_empty_parents)
247         {
248           ok &= remove_parents (dir);
249         }
250     }
251
252   exit (ok ? EXIT_SUCCESS : EXIT_FAILURE);
253 }