]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/shared/rm-rf.c
hwdb: Add mapping for Xiaomi Mipad 2 bottom bezel capacitive buttons
[thirdparty/systemd.git] / src / shared / rm-rf.c
1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3 #include <errno.h>
4 #include <fcntl.h>
5 #include <stdbool.h>
6 #include <stddef.h>
7 #include <unistd.h>
8
9 #include "alloc-util.h"
10 #include "btrfs-util.h"
11 #include "cgroup-util.h"
12 #include "dirent-util.h"
13 #include "fd-util.h"
14 #include "fs-util.h"
15 #include "log.h"
16 #include "macro.h"
17 #include "mountpoint-util.h"
18 #include "path-util.h"
19 #include "rm-rf.h"
20 #include "stat-util.h"
21 #include "string-util.h"
22
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. */
26 static bool is_physical_fs(const struct statfs *sfs) {
27 return !is_temporary_fs(sfs) && !is_cgroup_fs(sfs);
28 }
29
30 static int patch_dirfd_mode(
31 int dfd,
32 bool refuse_already_set,
33 mode_t *ret_old_mode) {
34
35 struct stat st;
36 int r;
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;
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
54 if (st.st_uid != geteuid()) /* this only works if the UID matches ours */
55 return -EACCES;
56
57 r = fchmod_opath(dfd, (st.st_mode | 0700) & 07777);
58 if (r < 0)
59 return r;
60
61 *ret_old_mode = st.st_mode;
62 return 1;
63 }
64
65 int unlinkat_harder(int dfd, const char *filename, int unlink_flags, RemoveFlags remove_flags) {
66 mode_t old_mode;
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
78 r = patch_dirfd_mode(dfd, /* refuse_already_set = */ true, &old_mode);
79 if (r < 0)
80 return r;
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 */
85 (void) fchmod(dfd, old_mode & 07777);
86 return r;
87 }
88
89 if (FLAGS_SET(remove_flags, REMOVE_CHMOD_RESTORE) && fchmod(dfd, old_mode & 07777) < 0)
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 */
94 return 0;
95 }
96
97 int fstatat_harder(int dfd,
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
113 r = patch_dirfd_mode(dfd, /* refuse_already_set = */ true, &old_mode);
114 if (r < 0)
115 return r;
116
117 if (fstatat(dfd, filename, ret, fstatat_flags) < 0) {
118 r = -errno;
119 (void) fchmod(dfd, old_mode & 07777);
120 return r;
121 }
122
123 if (FLAGS_SET(remove_flags, REMOVE_CHMOD_RESTORE) && fchmod(dfd, old_mode & 07777) < 0)
124 return -errno;
125
126 return 0;
127 }
128
129 static 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
185 static int rm_rf_children_impl(
186 int fd,
187 RemoveFlags flags,
188 const struct stat *root_dev,
189 mode_t old_mode);
190
191 static int rm_rf_inner_child(
192 int fd,
193 const char *fname,
194 int is_dir,
195 RemoveFlags flags,
196 const struct stat *root_dev,
197 bool allow_recursion) {
198
199 struct stat st;
200 int r, q = 0;
201
202 assert(fd >= 0);
203 assert(fname);
204
205 if (is_dir < 0 ||
206 root_dev ||
207 (is_dir > 0 && (root_dev || (flags & REMOVE_SUBVOLUME)))) {
208
209 r = fstatat_harder(fd, fname, &st, AT_SYMLINK_NOFOLLOW, flags);
210 if (r < 0)
211 return r;
212
213 is_dir = S_ISDIR(st.st_mode);
214 }
215
216 if (is_dir) {
217 /* If root_dev is set, remove subdirectories only if device is same */
218 if (root_dev && st.st_dev != root_dev->st_dev)
219 return 0;
220
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;
227
228 if ((flags & REMOVE_SUBVOLUME) && btrfs_might_be_subvol(&st)) {
229 /* This could be a subvolume, try to remove it */
230
231 r = btrfs_subvol_remove_at(fd, fname, BTRFS_REMOVE_RECURSIVE|BTRFS_REMOVE_QUOTA);
232 if (r < 0) {
233 if (!IN_SET(r, -ENOTTY, -EINVAL))
234 return r;
235
236 /* ENOTTY, then it wasn't a btrfs subvolume, continue below. */
237 } else
238 /* It was a subvolume, done. */
239 return 1;
240 }
241
242 if (!allow_recursion)
243 return -EISDIR;
244
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);
249 if (subdir_fd < 0)
250 return subdir_fd;
251
252 /* We pass REMOVE_PHYSICAL here, to avoid doing the fstatfs() to check the file system type
253 * again for each directory */
254 q = rm_rf_children_impl(subdir_fd, flags | REMOVE_PHYSICAL, root_dev, old_mode);
255
256 } else if (flags & REMOVE_ONLY_DIRECTORIES)
257 return 0;
258
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;
265 }
266
267 typedef struct TodoEntry {
268 DIR *dir; /* A directory that we were operating on. */
269 char *dirname; /* The filename of that directory itself. */
270 mode_t old_mode; /* The original file mode. */
271 } TodoEntry;
272
273 static 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
282 int rm_rf_children(
283 int fd,
284 RemoveFlags flags,
285 const struct stat *root_dev) {
286
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
297 static int rm_rf_children_impl(
298 int fd,
299 RemoveFlags flags,
300 const struct stat *root_dev,
301 mode_t old_mode) {
302
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 */
306 int ret = 0, r;
307
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);
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 }
326 dirname = mfree(dirname);
327
328 /* And now let's back out one level up */
329 n_todo--;
330 d = TAKE_PTR(todos[n_todo].dir);
331 dirname = TAKE_PTR(todos[n_todo].dirname);
332 old_mode = todos[n_todo].old_mode;
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 }
369
370 FOREACH_DIRENT_ALL(de, d, return -errno) {
371 int is_dir;
372
373 if (dot_or_dot_dot(de->d_name))
374 continue;
375
376 is_dir = de->d_type == DT_UNKNOWN ? -1 : de->d_type == DT_DIR;
377
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 */
381
382 if (!GREEDY_REALLOC0(todos, n_todo + 2))
383 return log_oom();
384
385 _cleanup_free_ char *newdirname = strdup(de->d_name);
386 if (!newdirname)
387 return log_oom();
388
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);
393 if (newfd >= 0) {
394 todos[n_todo++] = (TodoEntry) {
395 .dir = TAKE_PTR(d),
396 .dirname = TAKE_PTR(dirname),
397 .old_mode = old_mode
398 };
399
400 fd = newfd;
401 dirname = TAKE_PTR(newdirname);
402 old_mode = mode;
403
404 goto next_fd;
405
406 } else if (newfd != -ENOENT && ret == 0)
407 ret = newfd;
408
409 } else if (r < 0 && r != -ENOENT && ret == 0)
410 ret = r;
411 }
412
413 if (FLAGS_SET(flags, REMOVE_SYNCFS) && syncfs(fd) < 0 && ret >= 0)
414 ret = -errno;
415
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
421 break;
422 }
423 }
424
425 return ret;
426 }
427
428 int rm_rf_at(int dir_fd, const char *path, RemoveFlags flags) {
429 mode_t old_mode;
430 int fd, r, q = 0;
431
432 assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
433 assert(path);
434
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
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. */
442 if (path_is_root_at(dir_fd, path) > 0)
443 return log_error_errno(SYNTHETIC_ERRNO(EPERM),
444 "Attempted to remove entire root file system, and we can't allow that.");
445
446 if (FLAGS_SET(flags, REMOVE_SUBVOLUME | REMOVE_ROOT | REMOVE_PHYSICAL)) {
447 /* Try to remove as subvolume first */
448 r = btrfs_subvol_remove_at(dir_fd, path, BTRFS_REMOVE_RECURSIVE|BTRFS_REMOVE_QUOTA);
449 if (r >= 0)
450 return r;
451
452 if (FLAGS_SET(flags, REMOVE_MISSING_OK) && r == -ENOENT)
453 return 0;
454
455 if (!IN_SET(r, -ENOTTY, -EINVAL, -ENOTDIR))
456 return r;
457
458 /* Not btrfs or not a subvolume */
459 }
460
461 fd = openat_harder(dir_fd, path, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW|O_NOATIME, flags, &old_mode);
462 if (fd >= 0) {
463 /* We have a dir */
464 r = rm_rf_children_impl(fd, flags, NULL, old_mode);
465
466 if (FLAGS_SET(flags, REMOVE_ROOT))
467 q = RET_NERRNO(unlinkat(dir_fd, path, AT_REMOVEDIR));
468 } else {
469 r = fd;
470 if (FLAGS_SET(flags, REMOVE_MISSING_OK) && r == -ENOENT)
471 return 0;
472
473 if (!IN_SET(r, -ENOTDIR, -ELOOP))
474 return r;
475
476 if (FLAGS_SET(flags, REMOVE_ONLY_DIRECTORIES) || !FLAGS_SET(flags, REMOVE_ROOT))
477 return 0;
478
479 if (!FLAGS_SET(flags, REMOVE_PHYSICAL)) {
480 struct statfs s;
481
482 r = xstatfsat(dir_fd, path, &s);
483 if (r < 0)
484 return r;
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);
489 }
490
491 r = 0;
492 q = RET_NERRNO(unlinkat(dir_fd, path, 0));
493 }
494
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;
500 }
501
502 int 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
518 return rm_rf_inner_child(fd, name, -1, flags, NULL, true);
519 }