convert single-author programs to use proper_name
[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 /* The name this program was run with. */
42 char *program_name;
43
44 /* If true, remove empty parent directories.  */
45 static bool remove_empty_parents;
46
47 /* If true, don't treat failure to remove a nonempty directory
48    as an error.  */
49 static bool ignore_fail_on_non_empty;
50
51 /* If true, output a diagnostic for every directory processed.  */
52 static bool verbose;
53
54 /* For long options that have no equivalent short option, use a
55    non-character as a pseudo short option, starting with CHAR_MAX + 1.  */
56 enum
57 {
58   IGNORE_FAIL_ON_NON_EMPTY_OPTION = CHAR_MAX + 1
59 };
60
61 static struct option const longopts[] =
62 {
63   /* Don't name this `--force' because it's not close enough in meaning
64      to e.g. rm's -f option.  */
65   {"ignore-fail-on-non-empty", no_argument, NULL,
66    IGNORE_FAIL_ON_NON_EMPTY_OPTION},
67
68   {"path", no_argument, NULL, 'p'},  /* Deprecated.  */
69   {"parents", no_argument, NULL, 'p'},
70   {"verbose", no_argument, NULL, 'v'},
71   {GETOPT_HELP_OPTION_DECL},
72   {GETOPT_VERSION_OPTION_DECL},
73   {NULL, 0, NULL, 0}
74 };
75
76 /* Return true if ERROR_NUMBER is one of the values associated
77    with a failed rmdir due to non-empty target directory.  */
78 static bool
79 errno_rmdir_non_empty (int error_number)
80 {
81   return (error_number == RMDIR_ERRNO_NOT_EMPTY);
82 }
83
84 /* Return true if when rmdir fails with errno == ERROR_NUMBER
85    the directory may be empty.  */
86 static bool
87 errno_may_be_empty (int error_number)
88 {
89   switch (error_number)
90     {
91     case EACCES:
92     case EPERM:
93     case EROFS:
94     case EEXIST:
95     case EBUSY:
96       return true;
97     default:
98       return false;
99     }
100 }
101
102 /* Return true if an rmdir failure with errno == error_number
103    for DIR is ignorable.  */
104 static bool
105 ignorable_failure (int error_number, char const *dir)
106 {
107   return (ignore_fail_on_non_empty
108           && (errno_rmdir_non_empty (error_number)
109               || (errno_may_be_empty (error_number)
110                   && is_empty_dir (AT_FDCWD, dir))));
111 }
112
113 /* Remove any empty parent directories of DIR.
114    If DIR contains slash characters, at least one of them
115    (beginning with the rightmost) is replaced with a NUL byte.
116    Return true if successful.  */
117
118 static bool
119 remove_parents (char *dir)
120 {
121   char *slash;
122   bool ok = true;
123
124   strip_trailing_slashes (dir);
125   while (1)
126     {
127       slash = strrchr (dir, '/');
128       if (slash == NULL)
129         break;
130       /* Remove any characters after the slash, skipping any extra
131          slashes in a row. */
132       while (slash > dir && *slash == '/')
133         --slash;
134       slash[1] = 0;
135
136       /* Give a diagnostic for each attempted removal if --verbose.  */
137       if (verbose)
138         prog_fprintf (stdout, _("removing directory, %s"), quote (dir));
139
140       ok = (rmdir (dir) == 0);
141
142       if (!ok)
143         {
144           /* Stop quietly if --ignore-fail-on-non-empty. */
145           if (ignorable_failure (errno, dir))
146             {
147               ok = true;
148             }
149           else
150             {
151               /* Barring race conditions, DIR is expected to be a directory.  */
152               error (0, errno, _("failed to remove directory %s"),
153                      quote (dir));
154             }
155           break;
156         }
157     }
158   return ok;
159 }
160
161 void
162 usage (int status)
163 {
164   if (status != EXIT_SUCCESS)
165     fprintf (stderr, _("Try `%s --help' for more information.\n"),
166              program_name);
167   else
168     {
169       printf (_("Usage: %s [OPTION]... DIRECTORY...\n"), program_name);
170       fputs (_("\
171 Remove the DIRECTORY(ies), if they are empty.\n\
172 \n\
173       --ignore-fail-on-non-empty\n\
174                   ignore each failure that is solely because a directory\n\
175                   is non-empty\n\
176 "), stdout);
177       fputs (_("\
178   -p, --parents   Remove DIRECTORY and its ancestors.  E.g., `rmdir -p a/b/c' is\n\
179                   similar to `rmdir a/b/c a/b a'.\n\
180   -v, --verbose   output a diagnostic for every directory processed\n\
181 "), stdout);
182       fputs (HELP_OPTION_DESCRIPTION, stdout);
183       fputs (VERSION_OPTION_DESCRIPTION, stdout);
184       emit_bug_reporting_address ();
185     }
186   exit (status);
187 }
188
189 int
190 main (int argc, char **argv)
191 {
192   bool ok = true;
193   int optc;
194
195   initialize_main (&argc, &argv);
196   program_name = argv[0];
197   setlocale (LC_ALL, "");
198   bindtextdomain (PACKAGE, LOCALEDIR);
199   textdomain (PACKAGE);
200
201   atexit (close_stdout);
202
203   remove_empty_parents = false;
204
205   while ((optc = getopt_long (argc, argv, "pv", longopts, NULL)) != -1)
206     {
207       switch (optc)
208         {
209         case 'p':
210           remove_empty_parents = true;
211           break;
212         case IGNORE_FAIL_ON_NON_EMPTY_OPTION:
213           ignore_fail_on_non_empty = true;
214           break;
215         case 'v':
216           verbose = true;
217           break;
218         case_GETOPT_HELP_CHAR;
219         case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
220         default:
221           usage (EXIT_FAILURE);
222         }
223     }
224
225   if (optind == argc)
226     {
227       error (0, 0, _("missing operand"));
228       usage (EXIT_FAILURE);
229     }
230
231   for (; optind < argc; ++optind)
232     {
233       char *dir = argv[optind];
234
235       /* Give a diagnostic for each attempted removal if --verbose.  */
236       if (verbose)
237         prog_fprintf (stdout, _("removing directory, %s"), quote (dir));
238
239       if (rmdir (dir) != 0)
240         {
241           if (ignorable_failure (errno, dir))
242             continue;
243
244           /* Here, the diagnostic is less precise, since we have no idea
245              whether DIR is a directory.  */
246           error (0, errno, _("failed to remove %s"), quote (dir));
247           ok = false;
248         }
249       else if (remove_empty_parents)
250         {
251           ok &= remove_parents (dir);
252         }
253     }
254
255   exit (ok ? EXIT_SUCCESS : EXIT_FAILURE);
256 }