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/>. */
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> */
28 #include <sys/types.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
44 static bool ignore_fail_on_non_empty
;
46 /* If true, output a diagnostic for every directory processed. */
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. */
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
},
71 /* Return true if ERROR_NUMBER is one of the values associated
72 with a failed rmdir due to non-empty target directory. */
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. */
82 errno_may_be_non_empty (int error_number
)
96 /* Return true if an rmdir failure with errno == error_number
97 for DIR is ignorable. */
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. */
114 remove_parents (char *dir
)
119 strip_trailing_slashes (dir
);
122 slash
= strrchr (dir
, '/');
125 /* Remove any characters after the slash, skipping any extra
127 while (slash
> dir
&& *slash
== '/')
131 /* Give a diagnostic for each attempted removal if --verbose. */
133 prog_fprintf (stdout
, _("removing directory, %s"), quoteaf (dir
));
135 ok
= (rmdir (dir
) == 0);
136 int rmdir_errno
= errno
;
140 /* Stop quietly if --ignore-fail-on-non-empty. */
141 if (ignorable_failure (rmdir_errno
, dir
))
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");
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
));
170 if (status
!= EXIT_SUCCESS
)
174 printf (_("Usage: %s [OPTION]... DIRECTORY...\n"), program_name
);
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\
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\
188 fputs (HELP_OPTION_DESCRIPTION
, stdout
);
189 fputs (VERSION_OPTION_DESCRIPTION
, stdout
);
190 emit_ancillary_info (PROGRAM_NAME
);
196 main (int argc
, char **argv
)
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)
216 remove_empty_parents
= true;
218 case IGNORE_FAIL_ON_NON_EMPTY_OPTION
:
219 ignore_fail_on_non_empty
= true;
224 case_GETOPT_HELP_CHAR
;
225 case_GETOPT_VERSION_CHAR (PROGRAM_NAME
, AUTHORS
);
227 usage (EXIT_FAILURE
);
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. */
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
))
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'))
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
))
275 _("failed to remove %s:"
276 " Symbolic link not followed"),
286 error (0, rmdir_errno
, _("failed to remove %s"), quoteaf (dir
));
290 else if (remove_empty_parents
)
292 ok
&= remove_parents (dir
);
296 return ok
? EXIT_SUCCESS
: EXIT_FAILURE
;