]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/basic/rm-rf.c
blockdev-util: add correct API for detecting if block device has partition scanning...
[thirdparty/systemd.git] / src / basic / rm-rf.c
CommitLineData
53e1b683 1/* SPDX-License-Identifier: LGPL-2.1+ */
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"
93cc7779
TA
14#include "log.h"
15#include "macro.h"
049af8ad 16#include "mountpoint-util.h"
07630cea 17#include "path-util.h"
3ffd4af2 18#include "rm-rf.h"
8fcde012 19#include "stat-util.h"
07630cea 20#include "string-util.h"
c6878637 21
f0bef277
EV
22static bool is_physical_fs(const struct statfs *sfs) {
23 return !is_temporary_fs(sfs) && !is_cgroup_fs(sfs);
24}
25
c6878637
LP
26int rm_rf_children(int fd, RemoveFlags flags, struct stat *root_dev) {
27 _cleanup_closedir_ DIR *d = NULL;
8fb3f009 28 struct dirent *de;
c6878637 29 int ret = 0, r;
f0bef277 30 struct statfs sfs;
c6878637
LP
31
32 assert(fd >= 0);
33
c0ba6b92
LP
34 /* This returns the first error we run into, but nevertheless tries to go on. This closes the passed
35 * fd, in all cases, including on failure.. */
c6878637
LP
36
37 if (!(flags & REMOVE_PHYSICAL)) {
38
f0bef277 39 r = fstatfs(fd, &sfs);
c6878637
LP
40 if (r < 0) {
41 safe_close(fd);
f0bef277 42 return -errno;
c6878637
LP
43 }
44
f0bef277 45 if (is_physical_fs(&sfs)) {
265e9be7
ZJS
46 /* We refuse to clean physical file systems with this call,
47 * unless explicitly requested. This is extra paranoia just
48 * to be sure we never ever remove non-state data. */
49 _cleanup_free_ char *path = NULL;
50
51 (void) fd_get_path(fd, &path);
52 log_error("Attempted to remove disk file system under \"%s\", and we can't allow that.",
53 strna(path));
c6878637 54
c6878637
LP
55 safe_close(fd);
56 return -EPERM;
57 }
58 }
59
60 d = fdopendir(fd);
61 if (!d) {
62 safe_close(fd);
63 return errno == ENOENT ? 0 : -errno;
64 }
65
8fb3f009 66 FOREACH_DIRENT_ALL(de, d, return -errno) {
c6878637
LP
67 bool is_dir;
68 struct stat st;
69
49bfc877 70 if (dot_or_dot_dot(de->d_name))
c6878637
LP
71 continue;
72
9e9b663a
LP
73 if (de->d_type == DT_UNKNOWN ||
74 (de->d_type == DT_DIR && (root_dev || (flags & REMOVE_SUBVOLUME)))) {
c6878637
LP
75 if (fstatat(fd, de->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0) {
76 if (ret == 0 && errno != ENOENT)
77 ret = -errno;
78 continue;
79 }
80
81 is_dir = S_ISDIR(st.st_mode);
82 } else
83 is_dir = de->d_type == DT_DIR;
84
85 if (is_dir) {
50b6eec1 86 _cleanup_close_ int subdir_fd = -1;
c6878637 87
f25afeb6 88 /* if root_dev is set, remove subdirectories only if device is same */
c6878637
LP
89 if (root_dev && st.st_dev != root_dev->st_dev)
90 continue;
91
92 subdir_fd = openat(fd, de->d_name, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW|O_NOATIME);
93 if (subdir_fd < 0) {
94 if (ret == 0 && errno != ENOENT)
95 ret = -errno;
96 continue;
97 }
98
f25afeb6 99 /* Stop at mount points */
5d409034 100 r = fd_is_mount_point(fd, de->d_name, 0);
f25afeb6
LP
101 if (r < 0) {
102 if (ret == 0 && r != -ENOENT)
103 ret = r;
104
f25afeb6
LP
105 continue;
106 }
50b6eec1 107 if (r > 0)
f25afeb6 108 continue;
f25afeb6 109
9e9b663a 110 if ((flags & REMOVE_SUBVOLUME) && st.st_ino == 256) {
9e9b663a
LP
111
112 /* This could be a subvolume, try to remove it */
113
5bcd08db 114 r = btrfs_subvol_remove_fd(fd, de->d_name, BTRFS_REMOVE_RECURSIVE|BTRFS_REMOVE_QUOTA);
d9e2daaf 115 if (r < 0) {
4c701096 116 if (!IN_SET(r, -ENOTTY, -EINVAL)) {
9e9b663a 117 if (ret == 0)
d9e2daaf 118 ret = r;
9e9b663a 119
9e9b663a
LP
120 continue;
121 }
122
50b6eec1
LP
123 /* ENOTTY, then it wasn't a btrfs subvolume, continue below. */
124 } else
9e9b663a 125 /* It was a subvolume, continue. */
9e9b663a 126 continue;
9e9b663a
LP
127 }
128
50b6eec1 129 /* We pass REMOVE_PHYSICAL here, to avoid doing the fstatfs() to check the file
c6878637 130 * system type again for each directory */
50b6eec1 131 r = rm_rf_children(TAKE_FD(subdir_fd), flags | REMOVE_PHYSICAL, root_dev);
c6878637
LP
132 if (r < 0 && ret == 0)
133 ret = r;
134
135 if (unlinkat(fd, de->d_name, AT_REMOVEDIR) < 0) {
136 if (ret == 0 && errno != ENOENT)
137 ret = -errno;
138 }
139
140 } else if (!(flags & REMOVE_ONLY_DIRECTORIES)) {
141
142 if (unlinkat(fd, de->d_name, 0) < 0) {
143 if (ret == 0 && errno != ENOENT)
144 ret = -errno;
145 }
146 }
147 }
8fb3f009 148 return ret;
c6878637
LP
149}
150
151int rm_rf(const char *path, RemoveFlags flags) {
152 int fd, r;
153 struct statfs s;
154
155 assert(path);
156
c2f64c07
LP
157 /* For now, don't support dropping subvols when also only dropping directories, since we can't do
158 * this race-freely. */
159 if (FLAGS_SET(flags, REMOVE_ONLY_DIRECTORIES|REMOVE_SUBVOLUME))
160 return -EINVAL;
161
c0228b4f
LP
162 /* We refuse to clean the root file system with this call. This is extra paranoia to never cause a
163 * really seriously broken system. */
baaa35ad
ZJS
164 if (path_equal_or_files_same(path, "/", AT_SYMLINK_NOFOLLOW))
165 return log_error_errno(SYNTHETIC_ERRNO(EPERM),
166 "Attempted to remove entire root file system (\"%s\"), and we can't allow that.",
167 path);
c6878637 168
d94a24ca 169 if (FLAGS_SET(flags, REMOVE_SUBVOLUME | REMOVE_ROOT | REMOVE_PHYSICAL)) {
d9e2daaf 170 /* Try to remove as subvolume first */
5bcd08db 171 r = btrfs_subvol_remove(path, BTRFS_REMOVE_RECURSIVE|BTRFS_REMOVE_QUOTA);
d9e2daaf
LP
172 if (r >= 0)
173 return r;
174
c0228b4f
LP
175 if (FLAGS_SET(flags, REMOVE_MISSING_OK) && r == -ENOENT)
176 return 0;
177
4c701096 178 if (!IN_SET(r, -ENOTTY, -EINVAL, -ENOTDIR))
d9e2daaf
LP
179 return r;
180
181 /* Not btrfs or not a subvolume */
182 }
183
c6878637
LP
184 fd = open(path, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW|O_NOATIME);
185 if (fd < 0) {
c0228b4f
LP
186 if (FLAGS_SET(flags, REMOVE_MISSING_OK) && errno == ENOENT)
187 return 0;
188
ec2ce0c5 189 if (!IN_SET(errno, ENOTDIR, ELOOP))
c6878637
LP
190 return -errno;
191
c0228b4f
LP
192 if (FLAGS_SET(flags, REMOVE_ONLY_DIRECTORIES))
193 return 0;
c6878637 194
c0228b4f
LP
195 if (FLAGS_SET(flags, REMOVE_ROOT)) {
196
197 if (!FLAGS_SET(flags, REMOVE_PHYSICAL)) {
198 if (statfs(path, &s) < 0)
199 return -errno;
200
201 if (is_physical_fs(&s))
202 return log_error_errno(SYNTHETIC_ERRNO(EPERM),
203 "Attempted to remove files from a disk file system under \"%s\", refusing.",
204 path);
205 }
206
207 if (unlink(path) < 0) {
208 if (FLAGS_SET(flags, REMOVE_MISSING_OK) && errno == ENOENT)
209 return 0;
c6878637 210
c6878637 211 return -errno;
c0228b4f
LP
212 }
213 }
c6878637
LP
214
215 return 0;
216 }
217
218 r = rm_rf_children(fd, flags, NULL);
219
c0228b4f
LP
220 if (FLAGS_SET(flags, REMOVE_ROOT) &&
221 rmdir(path) < 0 &&
222 r >= 0 &&
223 (!FLAGS_SET(flags, REMOVE_MISSING_OK) || errno != ENOENT))
224 r = -errno;
c6878637
LP
225
226 return r;
227}