blob: 8b55ea2637e6164308b0f7a0ecc948a3f30a97db [file] [log] [blame]
Yu Watanabedb9ecf02020-11-09 13:23:58 +09001/* SPDX-License-Identifier: LGPL-2.1-or-later */
Lennart Poetteringc6878632015-04-04 11:52:57 +02002
Thomas Hindoe Paaboel Andersen11c3a362015-11-30 21:43:37 +01003#include <errno.h>
4#include <fcntl.h>
5#include <stdbool.h>
6#include <stddef.h>
Thomas Hindoe Paaboel Andersen11c3a362015-11-30 21:43:37 +01007#include <unistd.h>
8
Zbigniew Jędrzejewski-Szmek265e9be2018-05-22 11:33:01 +02009#include "alloc-util.h"
Lennart Poetteringd9e2daa2015-04-06 10:57:17 +020010#include "btrfs-util.h"
Evgeny Vereshchaginf0bef272016-10-13 16:50:46 +030011#include "cgroup-util.h"
Reverend Homer8fb3f002016-12-09 12:04:30 +030012#include "dirent-util.h"
Lennart Poettering3ffd4af2015-10-25 13:14:12 +010013#include "fd-util.h"
Thomas Hindoe Paaboel Andersen93cc7772015-12-01 23:22:03 +010014#include "log.h"
15#include "macro.h"
Zbigniew Jędrzejewski-Szmek049af8a2018-11-29 10:24:39 +010016#include "mountpoint-util.h"
Lennart Poettering07630ce2015-10-24 22:58:24 +020017#include "path-util.h"
Lennart Poettering3ffd4af2015-10-25 13:14:12 +010018#include "rm-rf.h"
Lennart Poettering8fcde012015-10-26 22:01:44 +010019#include "stat-util.h"
Lennart Poettering07630ce2015-10-24 22:58:24 +020020#include "string-util.h"
Lennart Poetteringc6878632015-04-04 11:52:57 +020021
Evgeny Vereshchaginf0bef272016-10-13 16:50:46 +030022static bool is_physical_fs(const struct statfs *sfs) {
23 return !is_temporary_fs(sfs) && !is_cgroup_fs(sfs);
24}
25
Allen Webb6b08fdf2021-06-28 11:07:59 -050026static int patch_dirfd_mode(
Lennart Poettering2899fb02020-07-23 15:24:54 +020027 int dfd,
Allen Webb6b08fdf2021-06-28 11:07:59 -050028 mode_t *ret_old_mode) {
Lennart Poettering2899fb02020-07-23 15:24:54 +020029
30 struct stat st;
Lennart Poettering2899fb02020-07-23 15:24:54 +020031
Allen Webb6b08fdf2021-06-28 11:07:59 -050032 assert(dfd >= 0);
33 assert(ret_old_mode);
Lennart Poettering2899fb02020-07-23 15:24:54 +020034
35 if (fstat(dfd, &st) < 0)
36 return -errno;
37 if (!S_ISDIR(st.st_mode))
38 return -ENOTDIR;
Frantisek Sumsal1d6cc5d2020-10-04 11:29:23 +020039 if (FLAGS_SET(st.st_mode, 0700)) /* Already set? */
Lennart Poettering2899fb02020-07-23 15:24:54 +020040 return -EACCES; /* original error */
41 if (st.st_uid != geteuid()) /* this only works if the UID matches ours */
42 return -EACCES;
43
44 if (fchmod(dfd, (st.st_mode | 0700) & 07777) < 0)
45 return -errno;
46
Allen Webb6b08fdf2021-06-28 11:07:59 -050047 *ret_old_mode = st.st_mode;
48 return 0;
49}
50
51int unlinkat_harder(int dfd, const char *filename, int unlink_flags, RemoveFlags remove_flags) {
52
53 mode_t old_mode;
54 int r;
55
56 /* Like unlinkat(), but tries harder: if we get EACCESS we'll try to set the r/w/x bits on the
57 * directory. This is useful if we run unprivileged and have some files where the w bit is
58 * missing. */
59
60 if (unlinkat(dfd, filename, unlink_flags) >= 0)
61 return 0;
62 if (errno != EACCES || !FLAGS_SET(remove_flags, REMOVE_CHMOD))
63 return -errno;
64
65 r = patch_dirfd_mode(dfd, &old_mode);
66 if (r < 0)
67 return r;
68
Lennart Poettering2899fb02020-07-23 15:24:54 +020069 if (unlinkat(dfd, filename, unlink_flags) < 0) {
70 r = -errno;
71 /* Try to restore the original access mode if this didn't work */
Allen Webb6b08fdf2021-06-28 11:07:59 -050072 (void) fchmod(dfd, old_mode);
Lennart Poettering2899fb02020-07-23 15:24:54 +020073 return r;
74 }
75
Allen Webb6b08fdf2021-06-28 11:07:59 -050076 if (FLAGS_SET(remove_flags, REMOVE_CHMOD_RESTORE) && fchmod(dfd, old_mode) < 0)
77 return -errno;
78
79 /* If this worked, we won't reset the old mode by default, since we'll need it for other entries too,
80 * and we should destroy the whole thing */
81 return 0;
82}
83
84int fstatat_harder(int dfd,
85 const char *filename,
86 struct stat *ret,
87 int fstatat_flags,
88 RemoveFlags remove_flags) {
89
90 mode_t old_mode;
91 int r;
92
93 /* Like unlink_harder() but does the same for fstatat() */
94
95 if (fstatat(dfd, filename, ret, fstatat_flags) >= 0)
96 return 0;
97 if (errno != EACCES || !FLAGS_SET(remove_flags, REMOVE_CHMOD))
98 return -errno;
99
100 r = patch_dirfd_mode(dfd, &old_mode);
101 if (r < 0)
102 return r;
103
104 if (fstatat(dfd, filename, ret, fstatat_flags) < 0) {
105 r = -errno;
106 (void) fchmod(dfd, old_mode);
107 return r;
108 }
109
110 if (FLAGS_SET(remove_flags, REMOVE_CHMOD_RESTORE) && fchmod(dfd, old_mode) < 0)
111 return -errno;
112
Lennart Poettering2899fb02020-07-23 15:24:54 +0200113 return 0;
114}
115
Lennart Poetteringc6878632015-04-04 11:52:57 +0200116int rm_rf_children(int fd, RemoveFlags flags, struct stat *root_dev) {
117 _cleanup_closedir_ DIR *d = NULL;
Reverend Homer8fb3f002016-12-09 12:04:30 +0300118 struct dirent *de;
Lennart Poetteringc6878632015-04-04 11:52:57 +0200119 int ret = 0, r;
Evgeny Vereshchaginf0bef272016-10-13 16:50:46 +0300120 struct statfs sfs;
Lennart Poetteringc6878632015-04-04 11:52:57 +0200121
122 assert(fd >= 0);
123
Lennart Poetteringc0ba6b92019-04-29 15:45:19 +0200124 /* This returns the first error we run into, but nevertheless tries to go on. This closes the passed
125 * fd, in all cases, including on failure.. */
Lennart Poetteringc6878632015-04-04 11:52:57 +0200126
127 if (!(flags & REMOVE_PHYSICAL)) {
128
Evgeny Vereshchaginf0bef272016-10-13 16:50:46 +0300129 r = fstatfs(fd, &sfs);
Lennart Poetteringc6878632015-04-04 11:52:57 +0200130 if (r < 0) {
131 safe_close(fd);
Evgeny Vereshchaginf0bef272016-10-13 16:50:46 +0300132 return -errno;
Lennart Poetteringc6878632015-04-04 11:52:57 +0200133 }
134
Evgeny Vereshchaginf0bef272016-10-13 16:50:46 +0300135 if (is_physical_fs(&sfs)) {
Zbigniew Jędrzejewski-Szmek265e9be2018-05-22 11:33:01 +0200136 /* We refuse to clean physical file systems with this call,
137 * unless explicitly requested. This is extra paranoia just
138 * to be sure we never ever remove non-state data. */
139 _cleanup_free_ char *path = NULL;
Lennart Poetteringc6878632015-04-04 11:52:57 +0200140
Zbigniew Jędrzejewski-Szmek265e9be2018-05-22 11:33:01 +0200141 (void) fd_get_path(fd, &path);
142 log_error("Attempted to remove disk file system under \"%s\", and we can't allow that.",
143 strna(path));
144
Lennart Poetteringc6878632015-04-04 11:52:57 +0200145 safe_close(fd);
146 return -EPERM;
147 }
148 }
149
150 d = fdopendir(fd);
151 if (!d) {
152 safe_close(fd);
153 return errno == ENOENT ? 0 : -errno;
154 }
155
Reverend Homer8fb3f002016-12-09 12:04:30 +0300156 FOREACH_DIRENT_ALL(de, d, return -errno) {
Lennart Poetteringc6878632015-04-04 11:52:57 +0200157 bool is_dir;
158 struct stat st;
159
Lennart Poettering49bfc872017-02-02 00:06:18 +0100160 if (dot_or_dot_dot(de->d_name))
Lennart Poetteringc6878632015-04-04 11:52:57 +0200161 continue;
162
Lennart Poettering9e9b6632015-04-04 19:22:00 +0200163 if (de->d_type == DT_UNKNOWN ||
164 (de->d_type == DT_DIR && (root_dev || (flags & REMOVE_SUBVOLUME)))) {
Lennart Poetteringc6878632015-04-04 11:52:57 +0200165 if (fstatat(fd, de->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0) {
166 if (ret == 0 && errno != ENOENT)
167 ret = -errno;
168 continue;
169 }
170
171 is_dir = S_ISDIR(st.st_mode);
172 } else
173 is_dir = de->d_type == DT_DIR;
174
175 if (is_dir) {
Lennart Poettering50b6eec2019-04-29 16:21:01 +0200176 _cleanup_close_ int subdir_fd = -1;
Lennart Poetteringc6878632015-04-04 11:52:57 +0200177
Lennart Poetteringf25afeb2015-04-04 14:42:39 +0200178 /* if root_dev is set, remove subdirectories only if device is same */
Lennart Poetteringc6878632015-04-04 11:52:57 +0200179 if (root_dev && st.st_dev != root_dev->st_dev)
180 continue;
181
182 subdir_fd = openat(fd, de->d_name, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW|O_NOATIME);
183 if (subdir_fd < 0) {
184 if (ret == 0 && errno != ENOENT)
185 ret = -errno;
186 continue;
187 }
188
Lennart Poetteringf25afeb2015-04-04 14:42:39 +0200189 /* Stop at mount points */
Martin Pitt5d409032015-05-27 09:56:03 +0200190 r = fd_is_mount_point(fd, de->d_name, 0);
Lennart Poetteringf25afeb2015-04-04 14:42:39 +0200191 if (r < 0) {
192 if (ret == 0 && r != -ENOENT)
193 ret = r;
194
Lennart Poetteringf25afeb2015-04-04 14:42:39 +0200195 continue;
196 }
Lennart Poettering50b6eec2019-04-29 16:21:01 +0200197 if (r > 0)
Lennart Poetteringf25afeb2015-04-04 14:42:39 +0200198 continue;
Lennart Poetteringf25afeb2015-04-04 14:42:39 +0200199
Lennart Poettering9e9b6632015-04-04 19:22:00 +0200200 if ((flags & REMOVE_SUBVOLUME) && st.st_ino == 256) {
Lennart Poettering9e9b6632015-04-04 19:22:00 +0200201
202 /* This could be a subvolume, try to remove it */
203
Lennart Poettering5bcd08d2015-10-21 19:38:21 +0200204 r = btrfs_subvol_remove_fd(fd, de->d_name, BTRFS_REMOVE_RECURSIVE|BTRFS_REMOVE_QUOTA);
Lennart Poetteringd9e2daa2015-04-06 10:57:17 +0200205 if (r < 0) {
Yu Watanabe4c701092017-10-04 23:01:32 +0900206 if (!IN_SET(r, -ENOTTY, -EINVAL)) {
Lennart Poettering9e9b6632015-04-04 19:22:00 +0200207 if (ret == 0)
Lennart Poetteringd9e2daa2015-04-06 10:57:17 +0200208 ret = r;
Lennart Poettering9e9b6632015-04-04 19:22:00 +0200209
Lennart Poettering9e9b6632015-04-04 19:22:00 +0200210 continue;
211 }
212
Lennart Poettering50b6eec2019-04-29 16:21:01 +0200213 /* ENOTTY, then it wasn't a btrfs subvolume, continue below. */
214 } else
Lennart Poettering9e9b6632015-04-04 19:22:00 +0200215 /* It was a subvolume, continue. */
Lennart Poettering9e9b6632015-04-04 19:22:00 +0200216 continue;
Lennart Poettering9e9b6632015-04-04 19:22:00 +0200217 }
218
Lennart Poettering50b6eec2019-04-29 16:21:01 +0200219 /* We pass REMOVE_PHYSICAL here, to avoid doing the fstatfs() to check the file
Lennart Poetteringc6878632015-04-04 11:52:57 +0200220 * system type again for each directory */
Lennart Poettering50b6eec2019-04-29 16:21:01 +0200221 r = rm_rf_children(TAKE_FD(subdir_fd), flags | REMOVE_PHYSICAL, root_dev);
Lennart Poetteringc6878632015-04-04 11:52:57 +0200222 if (r < 0 && ret == 0)
223 ret = r;
224
Lennart Poettering2899fb02020-07-23 15:24:54 +0200225 r = unlinkat_harder(fd, de->d_name, AT_REMOVEDIR, flags);
226 if (r < 0 && r != -ENOENT && ret == 0)
227 ret = r;
Lennart Poetteringc6878632015-04-04 11:52:57 +0200228
229 } else if (!(flags & REMOVE_ONLY_DIRECTORIES)) {
230
Lennart Poettering2899fb02020-07-23 15:24:54 +0200231 r = unlinkat_harder(fd, de->d_name, 0, flags);
232 if (r < 0 && r != -ENOENT && ret == 0)
233 ret = r;
Lennart Poetteringc6878632015-04-04 11:52:57 +0200234 }
235 }
Reverend Homer8fb3f002016-12-09 12:04:30 +0300236 return ret;
Lennart Poetteringc6878632015-04-04 11:52:57 +0200237}
238
239int rm_rf(const char *path, RemoveFlags flags) {
240 int fd, r;
241 struct statfs s;
242
243 assert(path);
244
Lennart Poetteringc2f64c02019-03-29 16:09:49 +0100245 /* For now, don't support dropping subvols when also only dropping directories, since we can't do
246 * this race-freely. */
247 if (FLAGS_SET(flags, REMOVE_ONLY_DIRECTORIES|REMOVE_SUBVOLUME))
248 return -EINVAL;
249
Lennart Poetteringc0228b42019-03-29 16:13:03 +0100250 /* We refuse to clean the root file system with this call. This is extra paranoia to never cause a
251 * really seriously broken system. */
Zbigniew Jędrzejewski-Szmekbaaa35a2018-11-20 23:40:44 +0100252 if (path_equal_or_files_same(path, "/", AT_SYMLINK_NOFOLLOW))
253 return log_error_errno(SYNTHETIC_ERRNO(EPERM),
254 "Attempted to remove entire root file system (\"%s\"), and we can't allow that.",
255 path);
Lennart Poetteringc6878632015-04-04 11:52:57 +0200256
Zbigniew Jędrzejewski-Szmekd94a24c2018-04-20 15:36:20 +0200257 if (FLAGS_SET(flags, REMOVE_SUBVOLUME | REMOVE_ROOT | REMOVE_PHYSICAL)) {
Lennart Poetteringd9e2daa2015-04-06 10:57:17 +0200258 /* Try to remove as subvolume first */
Lennart Poettering5bcd08d2015-10-21 19:38:21 +0200259 r = btrfs_subvol_remove(path, BTRFS_REMOVE_RECURSIVE|BTRFS_REMOVE_QUOTA);
Lennart Poetteringd9e2daa2015-04-06 10:57:17 +0200260 if (r >= 0)
261 return r;
262
Lennart Poetteringc0228b42019-03-29 16:13:03 +0100263 if (FLAGS_SET(flags, REMOVE_MISSING_OK) && r == -ENOENT)
264 return 0;
265
Yu Watanabe4c701092017-10-04 23:01:32 +0900266 if (!IN_SET(r, -ENOTTY, -EINVAL, -ENOTDIR))
Lennart Poetteringd9e2daa2015-04-06 10:57:17 +0200267 return r;
268
269 /* Not btrfs or not a subvolume */
270 }
271
Lennart Poetteringc6878632015-04-04 11:52:57 +0200272 fd = open(path, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW|O_NOATIME);
273 if (fd < 0) {
Lennart Poetteringc0228b42019-03-29 16:13:03 +0100274 if (FLAGS_SET(flags, REMOVE_MISSING_OK) && errno == ENOENT)
275 return 0;
276
Andreas Rammholdec2ce0c2017-09-29 09:58:22 +0200277 if (!IN_SET(errno, ENOTDIR, ELOOP))
Lennart Poetteringc6878632015-04-04 11:52:57 +0200278 return -errno;
279
Lennart Poetteringc0228b42019-03-29 16:13:03 +0100280 if (FLAGS_SET(flags, REMOVE_ONLY_DIRECTORIES))
281 return 0;
Lennart Poetteringc6878632015-04-04 11:52:57 +0200282
Lennart Poetteringc0228b42019-03-29 16:13:03 +0100283 if (FLAGS_SET(flags, REMOVE_ROOT)) {
284
285 if (!FLAGS_SET(flags, REMOVE_PHYSICAL)) {
286 if (statfs(path, &s) < 0)
287 return -errno;
288
289 if (is_physical_fs(&s))
290 return log_error_errno(SYNTHETIC_ERRNO(EPERM),
291 "Attempted to remove files from a disk file system under \"%s\", refusing.",
292 path);
293 }
294
295 if (unlink(path) < 0) {
296 if (FLAGS_SET(flags, REMOVE_MISSING_OK) && errno == ENOENT)
297 return 0;
298
299 return -errno;
300 }
Lennart Poetteringc6878632015-04-04 11:52:57 +0200301 }
302
Lennart Poetteringc6878632015-04-04 11:52:57 +0200303 return 0;
304 }
305
306 r = rm_rf_children(fd, flags, NULL);
307
Lennart Poetteringc0228b42019-03-29 16:13:03 +0100308 if (FLAGS_SET(flags, REMOVE_ROOT) &&
309 rmdir(path) < 0 &&
310 r >= 0 &&
311 (!FLAGS_SET(flags, REMOVE_MISSING_OK) || errno != ENOENT))
312 r = -errno;
Lennart Poetteringc6878632015-04-04 11:52:57 +0200313
314 return r;
315}