Line data Source code
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 "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 10 : errno_rmdir_non_empty (int error_number)
80 : {
81 10 : 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 10 : errno_may_be_empty (int error_number)
88 : {
89 10 : switch (error_number)
90 : {
91 2 : case EACCES:
92 : case EPERM:
93 : case EROFS:
94 : case EEXIST:
95 : case EBUSY:
96 2 : return true;
97 8 : default:
98 8 : 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 61 : ignorable_failure (int error_number, char const *dir)
106 : {
107 61 : return (ignore_fail_on_non_empty
108 61 : && (errno_rmdir_non_empty (error_number)
109 10 : || (errno_may_be_empty (error_number)
110 2 : && 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 1 : remove_parents (char *dir)
120 : {
121 : char *slash;
122 1 : bool ok = true;
123 :
124 1 : strip_trailing_slashes (dir);
125 : while (1)
126 : {
127 1 : slash = strrchr (dir, '/');
128 1 : if (slash == NULL)
129 1 : break;
130 : /* Remove any characters after the slash, skipping any extra
131 : slashes in a row. */
132 0 : while (slash > dir && *slash == '/')
133 0 : --slash;
134 0 : slash[1] = 0;
135 :
136 : /* Give a diagnostic for each attempted removal if --verbose. */
137 0 : if (verbose)
138 0 : prog_fprintf (stdout, _("removing directory, %s"), quote (dir));
139 :
140 0 : ok = (rmdir (dir) == 0);
141 :
142 0 : if (!ok)
143 : {
144 : /* Stop quietly if --ignore-fail-on-non-empty. */
145 0 : if (ignorable_failure (errno, dir))
146 : {
147 0 : ok = true;
148 : }
149 : else
150 : {
151 : /* Barring race conditions, DIR is expected to be a directory. */
152 0 : error (0, errno, _("failed to remove directory %s"),
153 : quote (dir));
154 : }
155 0 : break;
156 : }
157 : }
158 1 : return ok;
159 : }
160 :
161 : void
162 13 : usage (int status)
163 : {
164 13 : if (status != EXIT_SUCCESS)
165 12 : fprintf (stderr, _("Try `%s --help' for more information.\n"),
166 : program_name);
167 : else
168 : {
169 1 : printf (_("Usage: %s [OPTION]... DIRECTORY...\n"), program_name);
170 1 : 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 1 : 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 1 : fputs (HELP_OPTION_DESCRIPTION, stdout);
183 1 : fputs (VERSION_OPTION_DESCRIPTION, stdout);
184 1 : emit_bug_reporting_address ();
185 : }
186 13 : exit (status);
187 : }
188 :
189 : int
190 52 : main (int argc, char **argv)
191 : {
192 52 : bool ok = true;
193 : int optc;
194 :
195 : initialize_main (&argc, &argv);
196 52 : program_name = argv[0];
197 52 : setlocale (LC_ALL, "");
198 : bindtextdomain (PACKAGE, LOCALEDIR);
199 : textdomain (PACKAGE);
200 :
201 52 : atexit (close_stdout);
202 :
203 52 : remove_empty_parents = false;
204 :
205 122 : while ((optc = getopt_long (argc, argv, "pv", longopts, NULL)) != -1)
206 : {
207 25 : switch (optc)
208 : {
209 7 : case 'p':
210 7 : remove_empty_parents = true;
211 7 : break;
212 8 : case IGNORE_FAIL_ON_NON_EMPTY_OPTION:
213 8 : ignore_fail_on_non_empty = true;
214 8 : break;
215 3 : case 'v':
216 3 : verbose = true;
217 3 : break;
218 1 : case_GETOPT_HELP_CHAR;
219 1 : case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
220 5 : default:
221 5 : usage (EXIT_FAILURE);
222 : }
223 : }
224 :
225 45 : if (optind == argc)
226 : {
227 7 : error (0, 0, _("missing operand"));
228 7 : usage (EXIT_FAILURE);
229 : }
230 :
231 103 : for (; optind < argc; ++optind)
232 : {
233 65 : char *dir = argv[optind];
234 :
235 : /* Give a diagnostic for each attempted removal if --verbose. */
236 65 : if (verbose)
237 3 : prog_fprintf (stdout, _("removing directory, %s"), quote (dir));
238 :
239 65 : if (rmdir (dir) != 0)
240 : {
241 61 : if (ignorable_failure (errno, dir))
242 0 : continue;
243 :
244 : /* Here, the diagnostic is less precise, since we have no idea
245 : whether DIR is a directory. */
246 61 : error (0, errno, _("failed to remove %s"), quote (dir));
247 61 : ok = false;
248 : }
249 4 : else if (remove_empty_parents)
250 : {
251 1 : ok &= remove_parents (dir);
252 : }
253 : }
254 :
255 38 : exit (ok ? EXIT_SUCCESS : EXIT_FAILURE);
256 : }
|