dd: output final progress before syncing
[coreutils.git] / src / rmdir.c
blobf13aa8e0999f6d776b5eb1ce91371b983f049554
1 /* rmdir -- remove directories
3 Copyright (C) 1990-2022 Free Software Foundation, Inc.
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program. If not, see <https://www.gnu.org/licenses/>. */
18 /* Options:
19 -p, --parent Remove any parent dirs that are explicitly mentioned
20 in an argument, if they become empty after the
21 argument file is removed.
23 David MacKenzie <djm@ai.mit.edu> */
25 #include <config.h>
26 #include <stdio.h>
27 #include <getopt.h>
28 #include <sys/types.h>
30 #include "system.h"
31 #include "error.h"
32 #include "prog-fprintf.h"
34 /* The official name of this program (e.g., no 'g' prefix). */
35 #define PROGRAM_NAME "rmdir"
37 #define AUTHORS proper_name ("David MacKenzie")
39 /* If true, remove empty parent directories. */
40 static bool remove_empty_parents;
42 /* If true, don't treat failure to remove a nonempty directory
43 as an error. */
44 static bool ignore_fail_on_non_empty;
46 /* If true, output a diagnostic for every directory processed. */
47 static bool verbose;
49 /* For long options that have no equivalent short option, use a
50 non-character as a pseudo short option, starting with CHAR_MAX + 1. */
51 enum
53 IGNORE_FAIL_ON_NON_EMPTY_OPTION = CHAR_MAX + 1
56 static struct option const longopts[] =
58 /* Don't name this '--force' because it's not close enough in meaning
59 to e.g. rm's -f option. */
60 {"ignore-fail-on-non-empty", no_argument, NULL,
61 IGNORE_FAIL_ON_NON_EMPTY_OPTION},
63 {"path", no_argument, NULL, 'p'}, /* Deprecated. */
64 {"parents", no_argument, NULL, 'p'},
65 {"verbose", no_argument, NULL, 'v'},
66 {GETOPT_HELP_OPTION_DECL},
67 {GETOPT_VERSION_OPTION_DECL},
68 {NULL, 0, NULL, 0}
71 /* Return true if ERROR_NUMBER is one of the values associated
72 with a failed rmdir due to non-empty target directory. */
73 static bool
74 errno_rmdir_non_empty (int error_number)
76 return error_number == ENOTEMPTY || error_number == EEXIST;
79 /* Return true if when rmdir fails with errno == ERROR_NUMBER
80 the directory may be non empty. */
81 static bool
82 errno_may_be_non_empty (int error_number)
84 switch (error_number)
86 case EACCES:
87 case EPERM:
88 case EROFS:
89 case EBUSY:
90 return true;
91 default:
92 return false;
96 /* Return true if an rmdir failure with errno == error_number
97 for DIR is ignorable. */
98 static bool
99 ignorable_failure (int error_number, char const *dir)
101 return (ignore_fail_on_non_empty
102 && (errno_rmdir_non_empty (error_number)
103 || (errno_may_be_non_empty (error_number)
104 && ! is_empty_dir (AT_FDCWD, dir)
105 && errno == 0 /* definitely non empty */)));
108 /* Remove any empty parent directories of DIR.
109 If DIR contains slash characters, at least one of them
110 (beginning with the rightmost) is replaced with a NUL byte.
111 Return true if successful. */
113 static bool
114 remove_parents (char *dir)
116 char *slash;
117 bool ok = true;
119 strip_trailing_slashes (dir);
120 while (true)
122 slash = strrchr (dir, '/');
123 if (slash == NULL)
124 break;
125 /* Remove any characters after the slash, skipping any extra
126 slashes in a row. */
127 while (slash > dir && *slash == '/')
128 --slash;
129 slash[1] = 0;
131 /* Give a diagnostic for each attempted removal if --verbose. */
132 if (verbose)
133 prog_fprintf (stdout, _("removing directory, %s"), quoteaf (dir));
135 ok = (rmdir (dir) == 0);
136 int rmdir_errno = errno;
138 if (! ok)
140 /* Stop quietly if --ignore-fail-on-non-empty. */
141 if (ignorable_failure (rmdir_errno, dir))
143 ok = true;
145 else
147 char const *error_msg;
148 if (rmdir_errno != ENOTDIR)
150 /* Barring race conditions,
151 DIR is expected to be a directory. */
152 error_msg = N_("failed to remove directory %s");
154 else
156 /* A path component could be a symbolic link */
157 error_msg = N_("failed to remove %s");
159 error (0, rmdir_errno, _(error_msg), quoteaf (dir));
161 break;
164 return ok;
167 void
168 usage (int status)
170 if (status != EXIT_SUCCESS)
171 emit_try_help ();
172 else
174 printf (_("Usage: %s [OPTION]... DIRECTORY...\n"), program_name);
175 fputs (_("\
176 Remove the DIRECTORY(ies), if they are empty.\n\
178 --ignore-fail-on-non-empty\n\
179 ignore each failure that is solely because a directory\n\
180 is non-empty\n\
181 "), stdout);
182 fputs (_("\
183 -p, --parents remove DIRECTORY and its ancestors; e.g., 'rmdir -p a/b/c' is\
185 similar to 'rmdir a/b/c a/b a'\n\
186 -v, --verbose output a diagnostic for every directory processed\n\
187 "), stdout);
188 fputs (HELP_OPTION_DESCRIPTION, stdout);
189 fputs (VERSION_OPTION_DESCRIPTION, stdout);
190 emit_ancillary_info (PROGRAM_NAME);
192 exit (status);
196 main (int argc, char **argv)
198 bool ok = true;
199 int optc;
201 initialize_main (&argc, &argv);
202 set_program_name (argv[0]);
203 setlocale (LC_ALL, "");
204 bindtextdomain (PACKAGE, LOCALEDIR);
205 textdomain (PACKAGE);
207 atexit (close_stdout);
209 remove_empty_parents = false;
211 while ((optc = getopt_long (argc, argv, "pv", longopts, NULL)) != -1)
213 switch (optc)
215 case 'p':
216 remove_empty_parents = true;
217 break;
218 case IGNORE_FAIL_ON_NON_EMPTY_OPTION:
219 ignore_fail_on_non_empty = true;
220 break;
221 case 'v':
222 verbose = true;
223 break;
224 case_GETOPT_HELP_CHAR;
225 case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
226 default:
227 usage (EXIT_FAILURE);
231 if (optind == argc)
233 error (0, 0, _("missing operand"));
234 usage (EXIT_FAILURE);
237 for (; optind < argc; ++optind)
239 char *dir = argv[optind];
241 /* Give a diagnostic for each attempted removal if --verbose. */
242 if (verbose)
243 prog_fprintf (stdout, _("removing directory, %s"), quoteaf (dir));
245 if (rmdir (dir) != 0)
247 int rmdir_errno = errno;
248 if (ignorable_failure (rmdir_errno, dir))
249 continue;
251 /* Distinguish the case for a symlink with trailing slash.
252 On Linux, rmdir(2) confusingly does not follow the symlink,
253 thus giving the errno ENOTDIR, while on other systems the symlink
254 is followed. We don't provide consistent behavior here,
255 but at least we provide a more accurate error message. */
256 bool custom_error = false;
257 if (rmdir_errno == ENOTDIR)
259 char const *last_unix_slash = strrchr (dir, '/');
260 if (last_unix_slash && (*(last_unix_slash + 1) == '\0'))
262 struct stat st;
263 int ret = stat (dir, &st);
264 /* Some other issue following, or is actually a directory. */
265 if ((ret != 0 && errno != ENOTDIR)
266 || (ret == 0 && S_ISDIR (st.st_mode)))
268 /* Ensure the last component was a symlink. */
269 char *dir_arg = xstrdup (dir);
270 strip_trailing_slashes (dir);
271 ret = lstat (dir, &st);
272 if (ret == 0 && S_ISLNK (st.st_mode))
274 error (0, 0,
275 _("failed to remove %s:"
276 " Symbolic link not followed"),
277 quoteaf (dir_arg));
278 custom_error = true;
280 free (dir_arg);
285 if (! custom_error)
286 error (0, rmdir_errno, _("failed to remove %s"), quoteaf (dir));
288 ok = false;
290 else if (remove_empty_parents)
292 ok &= remove_parents (dir);
296 return ok ? EXIT_SUCCESS : EXIT_FAILURE;