]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/shared/rm-rf.c
hwdb: Add mapping for Xiaomi Mipad 2 bottom bezel capacitive buttons
[thirdparty/systemd.git] / src / shared / rm-rf.c
CommitLineData
db9ecf05 1/* SPDX-License-Identifier: LGPL-2.1-or-later */
c6878637 2
11c3a366
TA
3#include <errno.h>
4#include <fcntl.h>
5#include <stdbool.h>
6#include <stddef.h>
11c3a366
TA
7#include <unistd.h>
8
265e9be7 9#include "alloc-util.h"
d9e2daaf 10#include "btrfs-util.h"
f0bef277 11#include "cgroup-util.h"
8fb3f009 12#include "dirent-util.h"
3ffd4af2 13#include "fd-util.h"
7be96577 14#include "fs-util.h"
93cc7779
TA
15#include "log.h"
16#include "macro.h"
049af8ad 17#include "mountpoint-util.h"
07630cea 18#include "path-util.h"
3ffd4af2 19#include "rm-rf.h"
8fcde012 20#include "stat-util.h"
07630cea 21#include "string-util.h"
c6878637 22
bdbb61f6
YW
23/* We treat tmpfs/ramfs + cgroupfs as non-physical file systems. cgroupfs is similar to tmpfs in a way
24 * after all: we can create arbitrary directory hierarchies in it, and hence can also use rm_rf() on it
25 * to remove those again. */
f0bef277
EV
26static bool is_physical_fs(const struct statfs *sfs) {
27 return !is_temporary_fs(sfs) && !is_cgroup_fs(sfs);
28}
29
1b55621d
LP
30static int patch_dirfd_mode(
31 int dfd,
7be96577 32 bool refuse_already_set,
1b55621d
LP
33 mode_t *ret_old_mode) {
34
35 struct stat st;
7be96577 36 int r;
1b55621d
LP
37
38 assert(dfd >= 0);
39 assert(ret_old_mode);
40
41 if (fstat(dfd, &st) < 0)
42 return -errno;
43 if (!S_ISDIR(st.st_mode))
44 return -ENOTDIR;
7be96577
YW
45
46 if (FLAGS_SET(st.st_mode, 0700)) { /* Already set? */
47 if (refuse_already_set)
48 return -EACCES; /* original error */
49
50 *ret_old_mode = st.st_mode;
51 return 0;
52 }
53
1b55621d
LP
54 if (st.st_uid != geteuid()) /* this only works if the UID matches ours */
55 return -EACCES;
56
7be96577
YW
57 r = fchmod_opath(dfd, (st.st_mode | 0700) & 07777);
58 if (r < 0)
59 return r;
1b55621d
LP
60
61 *ret_old_mode = st.st_mode;
7be96577 62 return 1;
1b55621d
LP
63}
64
c46c3233 65int unlinkat_harder(int dfd, const char *filename, int unlink_flags, RemoveFlags remove_flags) {
1b55621d 66 mode_t old_mode;
2899fb02
LP
67 int r;
68
69 /* Like unlinkat(), but tries harder: if we get EACCESS we'll try to set the r/w/x bits on the
70 * directory. This is useful if we run unprivileged and have some files where the w bit is
71 * missing. */
72
73 if (unlinkat(dfd, filename, unlink_flags) >= 0)
74 return 0;
75 if (errno != EACCES || !FLAGS_SET(remove_flags, REMOVE_CHMOD))
76 return -errno;
77
7be96577 78 r = patch_dirfd_mode(dfd, /* refuse_already_set = */ true, &old_mode);
1b55621d
LP
79 if (r < 0)
80 return r;
2899fb02
LP
81
82 if (unlinkat(dfd, filename, unlink_flags) < 0) {
83 r = -errno;
84 /* Try to restore the original access mode if this didn't work */
da19c071 85 (void) fchmod(dfd, old_mode & 07777);
1b55621d
LP
86 return r;
87 }
88
da19c071 89 if (FLAGS_SET(remove_flags, REMOVE_CHMOD_RESTORE) && fchmod(dfd, old_mode & 07777) < 0)
c46c3233
AW
90 return -errno;
91
92 /* If this worked, we won't reset the old mode by default, since we'll need it for other entries too,
93 * and we should destroy the whole thing */
1b55621d
LP
94 return 0;
95}
96
c46c3233 97int fstatat_harder(int dfd,
1b55621d
LP
98 const char *filename,
99 struct stat *ret,
100 int fstatat_flags,
101 RemoveFlags remove_flags) {
102
103 mode_t old_mode;
104 int r;
105
106 /* Like unlink_harder() but does the same for fstatat() */
107
108 if (fstatat(dfd, filename, ret, fstatat_flags) >= 0)
109 return 0;
110 if (errno != EACCES || !FLAGS_SET(remove_flags, REMOVE_CHMOD))
111 return -errno;
112
7be96577 113 r = patch_dirfd_mode(dfd, /* refuse_already_set = */ true, &old_mode);
1b55621d
LP
114 if (r < 0)
115 return r;
116
117 if (fstatat(dfd, filename, ret, fstatat_flags) < 0) {
118 r = -errno;
da19c071 119 (void) fchmod(dfd, old_mode & 07777);
2899fb02
LP
120 return r;
121 }
122
da19c071 123 if (FLAGS_SET(remove_flags, REMOVE_CHMOD_RESTORE) && fchmod(dfd, old_mode & 07777) < 0)
c46c3233
AW
124 return -errno;
125
2899fb02
LP
126 return 0;
127}
128
7be96577
YW
129static int openat_harder(int dfd, const char *path, int open_flags, RemoveFlags remove_flags, mode_t *ret_old_mode) {
130 _cleanup_close_ int pfd = -EBADF, fd = -EBADF;
131 bool chmod_done = false;
132 mode_t old_mode;
133 int r;
134
135 assert(dfd >= 0 || dfd == AT_FDCWD);
136 assert(path);
137
138 /* Unlike unlink_harder() and fstatat_harder(), this chmod the specified path. */
139
140 if (FLAGS_SET(open_flags, O_PATH) ||
141 !FLAGS_SET(open_flags, O_DIRECTORY) ||
142 !FLAGS_SET(remove_flags, REMOVE_CHMOD)) {
143
144 fd = RET_NERRNO(openat(dfd, path, open_flags));
145 if (fd < 0)
146 return fd;
147
148 if (ret_old_mode) {
149 struct stat st;
150
151 if (fstat(fd, &st) < 0)
152 return -errno;
153
154 *ret_old_mode = st.st_mode;
155 }
156
157 return TAKE_FD(fd);
158 }
159
160 pfd = RET_NERRNO(openat(dfd, path, (open_flags & (O_CLOEXEC|O_DIRECTORY|O_NOFOLLOW)) | O_PATH));
161 if (pfd < 0)
162 return pfd;
163
164 if (FLAGS_SET(remove_flags, REMOVE_CHMOD)) {
165 r = patch_dirfd_mode(pfd, /* refuse_already_set = */ false, &old_mode);
166 if (r < 0)
167 return r;
168
169 chmod_done = r;
170 }
171
172 fd = fd_reopen(pfd, open_flags & ~O_NOFOLLOW);
173 if (fd < 0) {
174 if (chmod_done)
175 (void) fchmod_opath(pfd, old_mode & 07777);
176 return fd;
177 }
178
179 if (ret_old_mode)
180 *ret_old_mode = old_mode;
181
182 return TAKE_FD(fd);
183}
184
185static int rm_rf_children_impl(
186 int fd,
187 RemoveFlags flags,
188 const struct stat *root_dev,
189 mode_t old_mode);
190
5b1cf7a9 191static int rm_rf_inner_child(
1f0fb7d5
LP
192 int fd,
193 const char *fname,
194 int is_dir,
195 RemoveFlags flags,
5b1cf7a9
ZJS
196 const struct stat *root_dev,
197 bool allow_recursion) {
c6878637 198
1f0fb7d5 199 struct stat st;
3bac86ab 200 int r, q = 0;
c6878637 201
1f0fb7d5
LP
202 assert(fd >= 0);
203 assert(fname);
c6878637 204
160dadc0
ZJS
205 if (is_dir < 0 ||
206 root_dev ||
207 (is_dir > 0 && (root_dev || (flags & REMOVE_SUBVOLUME)))) {
c6878637 208
1f0fb7d5
LP
209 r = fstatat_harder(fd, fname, &st, AT_SYMLINK_NOFOLLOW, flags);
210 if (r < 0)
211 return r;
c6878637 212
1f0fb7d5
LP
213 is_dir = S_ISDIR(st.st_mode);
214 }
265e9be7 215
1f0fb7d5 216 if (is_dir) {
5b1cf7a9 217 /* If root_dev is set, remove subdirectories only if device is same */
1f0fb7d5
LP
218 if (root_dev && st.st_dev != root_dev->st_dev)
219 return 0;
c6878637 220
1f0fb7d5
LP
221 /* Stop at mount points */
222 r = fd_is_mount_point(fd, fname, 0);
223 if (r < 0)
224 return r;
225 if (r > 0)
226 return 0;
c6878637 227
1f0fb7d5 228 if ((flags & REMOVE_SUBVOLUME) && btrfs_might_be_subvol(&st)) {
1f0fb7d5 229 /* This could be a subvolume, try to remove it */
c6878637 230
24dbe603 231 r = btrfs_subvol_remove_at(fd, fname, BTRFS_REMOVE_RECURSIVE|BTRFS_REMOVE_QUOTA);
1b55621d 232 if (r < 0) {
1f0fb7d5
LP
233 if (!IN_SET(r, -ENOTTY, -EINVAL))
234 return r;
c6878637 235
1f0fb7d5
LP
236 /* ENOTTY, then it wasn't a btrfs subvolume, continue below. */
237 } else
238 /* It was a subvolume, done. */
239 return 1;
240 }
c6878637 241
5b1cf7a9
ZJS
242 if (!allow_recursion)
243 return -EISDIR;
244
7be96577
YW
245 mode_t old_mode;
246 int subdir_fd = openat_harder(fd, fname,
247 O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW|O_NOATIME,
248 flags, &old_mode);
1f0fb7d5 249 if (subdir_fd < 0)
7be96577 250 return subdir_fd;
c6878637 251
1f0fb7d5
LP
252 /* We pass REMOVE_PHYSICAL here, to avoid doing the fstatfs() to check the file system type
253 * again for each directory */
7be96577 254 q = rm_rf_children_impl(subdir_fd, flags | REMOVE_PHYSICAL, root_dev, old_mode);
c6878637 255
3bac86ab
ZJS
256 } else if (flags & REMOVE_ONLY_DIRECTORIES)
257 return 0;
9e9b663a 258
3bac86ab
ZJS
259 r = unlinkat_harder(fd, fname, is_dir ? AT_REMOVEDIR : 0, flags);
260 if (r < 0)
261 return r;
262 if (q < 0)
263 return q;
264 return 1;
1f0fb7d5 265}
9e9b663a 266
5b1cf7a9
ZJS
267typedef struct TodoEntry {
268 DIR *dir; /* A directory that we were operating on. */
269 char *dirname; /* The filename of that directory itself. */
7be96577 270 mode_t old_mode; /* The original file mode. */
5b1cf7a9
ZJS
271} TodoEntry;
272
273static void free_todo_entries(TodoEntry **todos) {
274 for (TodoEntry *x = *todos; x && x->dir; x++) {
275 closedir(x->dir);
276 free(x->dirname);
277 }
278
279 freep(todos);
280}
281
1f0fb7d5
LP
282int rm_rf_children(
283 int fd,
284 RemoveFlags flags,
285 const struct stat *root_dev) {
9e9b663a 286
7be96577
YW
287 struct stat st;
288
289 assert(fd >= 0);
290
291 if (fstat(fd, &st) < 0)
292 return -errno;
293
294 return rm_rf_children_impl(fd, flags, root_dev, st.st_mode);
295}
296
297static int rm_rf_children_impl(
298 int fd,
299 RemoveFlags flags,
300 const struct stat *root_dev,
301 mode_t old_mode) {
302
5b1cf7a9
ZJS
303 _cleanup_(free_todo_entries) TodoEntry *todos = NULL;
304 size_t n_todo = 0;
305 _cleanup_free_ char *dirname = NULL; /* Set when we are recursing and want to delete ourselves */
1f0fb7d5 306 int ret = 0, r;
9e9b663a 307
5b1cf7a9
ZJS
308 /* Return the first error we run into, but nevertheless try to go on.
309 * The passed fd is closed in all cases, including on failure. */
310
311 for (;;) { /* This loop corresponds to the directory nesting level. */
312 _cleanup_closedir_ DIR *d = NULL;
313
314 if (n_todo > 0) {
315 /* We know that we are in recursion here, because n_todo is set.
316 * We need to remove the inner directory we were operating on. */
317 assert(dirname);
318 r = unlinkat_harder(dirfd(todos[n_todo-1].dir), dirname, AT_REMOVEDIR, flags);
7be96577
YW
319 if (r < 0 && r != -ENOENT) {
320 if (ret == 0)
321 ret = r;
322
323 if (FLAGS_SET(flags, REMOVE_CHMOD_RESTORE))
324 (void) fchmodat(dirfd(todos[n_todo-1].dir), dirname, old_mode & 07777, 0);
325 }
5b1cf7a9
ZJS
326 dirname = mfree(dirname);
327
328 /* And now let's back out one level up */
b3a9d980 329 n_todo--;
5b1cf7a9
ZJS
330 d = TAKE_PTR(todos[n_todo].dir);
331 dirname = TAKE_PTR(todos[n_todo].dirname);
7be96577 332 old_mode = todos[n_todo].old_mode;
5b1cf7a9
ZJS
333
334 assert(d);
335 fd = dirfd(d); /* Retrieve the file descriptor from the DIR object */
336 assert(fd >= 0);
337 } else {
338 next_fd:
339 assert(fd >= 0);
340 d = fdopendir(fd);
341 if (!d) {
342 safe_close(fd);
343 return -errno;
344 }
345 fd = dirfd(d); /* We donated the fd to fdopendir(). Let's make sure we sure we have
346 * the right descriptor even if it were to internally invalidate the
347 * one we passed. */
348
349 if (!(flags & REMOVE_PHYSICAL)) {
350 struct statfs sfs;
351
352 if (fstatfs(fd, &sfs) < 0)
353 return -errno;
354
355 if (is_physical_fs(&sfs)) {
356 /* We refuse to clean physical file systems with this call, unless
357 * explicitly requested. This is extra paranoia just to be sure we
358 * never ever remove non-state data. */
359
360 _cleanup_free_ char *path = NULL;
361
362 (void) fd_get_path(fd, &path);
363 return log_error_errno(SYNTHETIC_ERRNO(EPERM),
364 "Attempted to remove disk file system under \"%s\", and we can't allow that.",
365 strna(path));
366 }
367 }
368 }
1f0fb7d5 369
5b1cf7a9
ZJS
370 FOREACH_DIRENT_ALL(de, d, return -errno) {
371 int is_dir;
1f0fb7d5 372
5b1cf7a9
ZJS
373 if (dot_or_dot_dot(de->d_name))
374 continue;
9e9b663a 375
5b1cf7a9 376 is_dir = de->d_type == DT_UNKNOWN ? -1 : de->d_type == DT_DIR;
c6878637 377
5b1cf7a9
ZJS
378 r = rm_rf_inner_child(fd, de->d_name, is_dir, flags, root_dev, false);
379 if (r == -EISDIR) {
380 /* Push the current working state onto the todo list */
1f0fb7d5 381
5b1cf7a9
ZJS
382 if (!GREEDY_REALLOC0(todos, n_todo + 2))
383 return log_oom();
c6878637 384
5b1cf7a9
ZJS
385 _cleanup_free_ char *newdirname = strdup(de->d_name);
386 if (!newdirname)
387 return log_oom();
c6878637 388
7be96577
YW
389 mode_t mode;
390 int newfd = openat_harder(fd, de->d_name,
391 O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW|O_NOATIME,
392 flags, &mode);
5b1cf7a9 393 if (newfd >= 0) {
7be96577
YW
394 todos[n_todo++] = (TodoEntry) {
395 .dir = TAKE_PTR(d),
396 .dirname = TAKE_PTR(dirname),
397 .old_mode = old_mode
398 };
399
5b1cf7a9
ZJS
400 fd = newfd;
401 dirname = TAKE_PTR(newdirname);
7be96577 402 old_mode = mode;
1f0fb7d5 403
5b1cf7a9 404 goto next_fd;
1f0fb7d5 405
cd2cd095
YW
406 } else if (newfd != -ENOENT && ret == 0)
407 ret = newfd;
1f0fb7d5 408
5b1cf7a9
ZJS
409 } else if (r < 0 && r != -ENOENT && ret == 0)
410 ret = r;
411 }
1f0fb7d5 412
5b1cf7a9
ZJS
413 if (FLAGS_SET(flags, REMOVE_SYNCFS) && syncfs(fd) < 0 && ret >= 0)
414 ret = -errno;
1f0fb7d5 415
7be96577
YW
416 if (n_todo == 0) {
417 if (FLAGS_SET(flags, REMOVE_CHMOD_RESTORE) &&
418 fchmod(fd, old_mode & 07777) < 0 && ret >= 0)
419 ret = -errno;
420
5b1cf7a9 421 break;
7be96577 422 }
5b1cf7a9 423 }
bdfe7ada 424
8fb3f009 425 return ret;
c6878637
LP
426}
427
5124aa8c 428int rm_rf_at(int dir_fd, const char *path, RemoveFlags flags) {
7be96577 429 mode_t old_mode;
84ced330 430 int fd, r, q = 0;
c6878637 431
5124aa8c 432 assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
a6ae9936 433 assert(path);
c6878637 434
c2f64c07
LP
435 /* For now, don't support dropping subvols when also only dropping directories, since we can't do
436 * this race-freely. */
437 if (FLAGS_SET(flags, REMOVE_ONLY_DIRECTORIES|REMOVE_SUBVOLUME))
438 return -EINVAL;
439
c0228b4f
LP
440 /* We refuse to clean the root file system with this call. This is extra paranoia to never cause a
441 * really seriously broken system. */
5124aa8c 442 if (path_is_root_at(dir_fd, path) > 0)
baaa35ad 443 return log_error_errno(SYNTHETIC_ERRNO(EPERM),
5124aa8c 444 "Attempted to remove entire root file system, and we can't allow that.");
c6878637 445
d94a24ca 446 if (FLAGS_SET(flags, REMOVE_SUBVOLUME | REMOVE_ROOT | REMOVE_PHYSICAL)) {
d9e2daaf 447 /* Try to remove as subvolume first */
5124aa8c 448 r = btrfs_subvol_remove_at(dir_fd, path, BTRFS_REMOVE_RECURSIVE|BTRFS_REMOVE_QUOTA);
d9e2daaf
LP
449 if (r >= 0)
450 return r;
451
c0228b4f
LP
452 if (FLAGS_SET(flags, REMOVE_MISSING_OK) && r == -ENOENT)
453 return 0;
454
4c701096 455 if (!IN_SET(r, -ENOTTY, -EINVAL, -ENOTDIR))
d9e2daaf
LP
456 return r;
457
458 /* Not btrfs or not a subvolume */
459 }
460
5124aa8c 461 fd = openat_harder(dir_fd, path, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW|O_NOATIME, flags, &old_mode);
84ced330
ZJS
462 if (fd >= 0) {
463 /* We have a dir */
7be96577 464 r = rm_rf_children_impl(fd, flags, NULL, old_mode);
84ced330
ZJS
465
466 if (FLAGS_SET(flags, REMOVE_ROOT))
5124aa8c 467 q = RET_NERRNO(unlinkat(dir_fd, path, AT_REMOVEDIR));
84ced330 468 } else {
7be96577
YW
469 r = fd;
470 if (FLAGS_SET(flags, REMOVE_MISSING_OK) && r == -ENOENT)
c0228b4f
LP
471 return 0;
472
7be96577
YW
473 if (!IN_SET(r, -ENOTDIR, -ELOOP))
474 return r;
c6878637 475
84ced330 476 if (FLAGS_SET(flags, REMOVE_ONLY_DIRECTORIES) || !FLAGS_SET(flags, REMOVE_ROOT))
c0228b4f 477 return 0;
c6878637 478
84ced330
ZJS
479 if (!FLAGS_SET(flags, REMOVE_PHYSICAL)) {
480 struct statfs s;
c6878637 481
5124aa8c
DDM
482 r = xstatfsat(dir_fd, path, &s);
483 if (r < 0)
484 return r;
84ced330
ZJS
485 if (is_physical_fs(&s))
486 return log_error_errno(SYNTHETIC_ERRNO(EPERM),
487 "Attempted to remove files from a disk file system under \"%s\", refusing.",
488 path);
c0228b4f 489 }
c6878637 490
84ced330 491 r = 0;
5124aa8c 492 q = RET_NERRNO(unlinkat(dir_fd, path, 0));
c6878637
LP
493 }
494
84ced330
ZJS
495 if (r < 0)
496 return r;
497 if (q < 0 && (q != -ENOENT || !FLAGS_SET(flags, REMOVE_MISSING_OK)))
498 return q;
499 return 0;
c6878637 500}
1f0fb7d5
LP
501
502int rm_rf_child(int fd, const char *name, RemoveFlags flags) {
503
504 /* Removes one specific child of the specified directory */
505
506 if (fd < 0)
507 return -EBADF;
508
509 if (!filename_is_valid(name))
510 return -EINVAL;
511
512 if ((flags & (REMOVE_ROOT|REMOVE_MISSING_OK)) != 0) /* Doesn't really make sense here, we are not supposed to remove 'fd' anyway */
513 return -EINVAL;
514
515 if (FLAGS_SET(flags, REMOVE_ONLY_DIRECTORIES|REMOVE_SUBVOLUME))
516 return -EINVAL;
517
5b1cf7a9 518 return rm_rf_inner_child(fd, name, -1, flags, NULL, true);
1f0fb7d5 519}