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