]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/basic/rm-rf.c
Add SPDX license identifiers to source files under the LGPL
[thirdparty/systemd.git] / src / basic / rm-rf.c
1 /* SPDX-License-Identifier: LGPL-2.1+ */
2 /***
3 This file is part of systemd.
4
5 Copyright 2015 Lennart Poettering
6
7 systemd is free software; you can redistribute it and/or modify it
8 under the terms of the GNU Lesser General Public License as published by
9 the Free Software Foundation; either version 2.1 of the License, or
10 (at your option) any later version.
11
12 systemd is distributed in the hope that it will be useful, but
13 WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 Lesser General Public License for more details.
16
17 You should have received a copy of the GNU Lesser General Public License
18 along with systemd; If not, see <http://www.gnu.org/licenses/>.
19 ***/
20
21 #include <errno.h>
22 #include <fcntl.h>
23 #include <stdbool.h>
24 #include <stddef.h>
25 #include <sys/stat.h>
26 #include <sys/statfs.h>
27 #include <unistd.h>
28
29 #include "btrfs-util.h"
30 #include "cgroup-util.h"
31 #include "dirent-util.h"
32 #include "fd-util.h"
33 #include "log.h"
34 #include "macro.h"
35 #include "mount-util.h"
36 #include "path-util.h"
37 #include "rm-rf.h"
38 #include "stat-util.h"
39 #include "string-util.h"
40
41 static bool is_physical_fs(const struct statfs *sfs) {
42 return !is_temporary_fs(sfs) && !is_cgroup_fs(sfs);
43 }
44
45 int rm_rf_children(int fd, RemoveFlags flags, struct stat *root_dev) {
46 _cleanup_closedir_ DIR *d = NULL;
47 struct dirent *de;
48 int ret = 0, r;
49 struct statfs sfs;
50
51 assert(fd >= 0);
52
53 /* This returns the first error we run into, but nevertheless
54 * tries to go on. This closes the passed fd. */
55
56 if (!(flags & REMOVE_PHYSICAL)) {
57
58 r = fstatfs(fd, &sfs);
59 if (r < 0) {
60 safe_close(fd);
61 return -errno;
62 }
63
64 if (is_physical_fs(&sfs)) {
65 /* We refuse to clean physical file systems
66 * with this call, unless explicitly
67 * requested. This is extra paranoia just to
68 * be sure we never ever remove non-state
69 * data */
70
71 log_error("Attempted to remove disk file system, and we can't allow that.");
72 safe_close(fd);
73 return -EPERM;
74 }
75 }
76
77 d = fdopendir(fd);
78 if (!d) {
79 safe_close(fd);
80 return errno == ENOENT ? 0 : -errno;
81 }
82
83 FOREACH_DIRENT_ALL(de, d, return -errno) {
84 bool is_dir;
85 struct stat st;
86
87 if (dot_or_dot_dot(de->d_name))
88 continue;
89
90 if (de->d_type == DT_UNKNOWN ||
91 (de->d_type == DT_DIR && (root_dev || (flags & REMOVE_SUBVOLUME)))) {
92 if (fstatat(fd, de->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0) {
93 if (ret == 0 && errno != ENOENT)
94 ret = -errno;
95 continue;
96 }
97
98 is_dir = S_ISDIR(st.st_mode);
99 } else
100 is_dir = de->d_type == DT_DIR;
101
102 if (is_dir) {
103 int subdir_fd;
104
105 /* if root_dev is set, remove subdirectories only if device is same */
106 if (root_dev && st.st_dev != root_dev->st_dev)
107 continue;
108
109 subdir_fd = openat(fd, de->d_name, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW|O_NOATIME);
110 if (subdir_fd < 0) {
111 if (ret == 0 && errno != ENOENT)
112 ret = -errno;
113 continue;
114 }
115
116 /* Stop at mount points */
117 r = fd_is_mount_point(fd, de->d_name, 0);
118 if (r < 0) {
119 if (ret == 0 && r != -ENOENT)
120 ret = r;
121
122 safe_close(subdir_fd);
123 continue;
124 }
125 if (r) {
126 safe_close(subdir_fd);
127 continue;
128 }
129
130 if ((flags & REMOVE_SUBVOLUME) && st.st_ino == 256) {
131
132 /* This could be a subvolume, try to remove it */
133
134 r = btrfs_subvol_remove_fd(fd, de->d_name, BTRFS_REMOVE_RECURSIVE|BTRFS_REMOVE_QUOTA);
135 if (r < 0) {
136 if (!IN_SET(r, -ENOTTY, -EINVAL)) {
137 if (ret == 0)
138 ret = r;
139
140 safe_close(subdir_fd);
141 continue;
142 }
143
144 /* ENOTTY, then it wasn't a
145 * btrfs subvolume, continue
146 * below. */
147 } else {
148 /* It was a subvolume, continue. */
149 safe_close(subdir_fd);
150 continue;
151 }
152 }
153
154 /* We pass REMOVE_PHYSICAL here, to avoid
155 * doing the fstatfs() to check the file
156 * system type again for each directory */
157 r = rm_rf_children(subdir_fd, flags | REMOVE_PHYSICAL, root_dev);
158 if (r < 0 && ret == 0)
159 ret = r;
160
161 if (unlinkat(fd, de->d_name, AT_REMOVEDIR) < 0) {
162 if (ret == 0 && errno != ENOENT)
163 ret = -errno;
164 }
165
166 } else if (!(flags & REMOVE_ONLY_DIRECTORIES)) {
167
168 if (unlinkat(fd, de->d_name, 0) < 0) {
169 if (ret == 0 && errno != ENOENT)
170 ret = -errno;
171 }
172 }
173 }
174 return ret;
175 }
176
177 int rm_rf(const char *path, RemoveFlags flags) {
178 int fd, r;
179 struct statfs s;
180
181 assert(path);
182
183 /* We refuse to clean the root file system with this
184 * call. This is extra paranoia to never cause a really
185 * seriously broken system. */
186 if (path_equal_or_files_same(path, "/", AT_SYMLINK_NOFOLLOW)) {
187 log_error("Attempted to remove entire root file system, and we can't allow that.");
188 return -EPERM;
189 }
190
191 if ((flags & (REMOVE_SUBVOLUME|REMOVE_ROOT|REMOVE_PHYSICAL)) == (REMOVE_SUBVOLUME|REMOVE_ROOT|REMOVE_PHYSICAL)) {
192 /* Try to remove as subvolume first */
193 r = btrfs_subvol_remove(path, BTRFS_REMOVE_RECURSIVE|BTRFS_REMOVE_QUOTA);
194 if (r >= 0)
195 return r;
196
197 if (!IN_SET(r, -ENOTTY, -EINVAL, -ENOTDIR))
198 return r;
199
200 /* Not btrfs or not a subvolume */
201 }
202
203 fd = open(path, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW|O_NOATIME);
204 if (fd < 0) {
205
206 if (!IN_SET(errno, ENOTDIR, ELOOP))
207 return -errno;
208
209 if (!(flags & REMOVE_PHYSICAL)) {
210 if (statfs(path, &s) < 0)
211 return -errno;
212
213 if (is_physical_fs(&s)) {
214 log_error("Attempted to remove disk file system, and we can't allow that.");
215 return -EPERM;
216 }
217 }
218
219 if ((flags & REMOVE_ROOT) && !(flags & REMOVE_ONLY_DIRECTORIES))
220 if (unlink(path) < 0 && errno != ENOENT)
221 return -errno;
222
223 return 0;
224 }
225
226 r = rm_rf_children(fd, flags, NULL);
227
228 if (flags & REMOVE_ROOT) {
229 if (rmdir(path) < 0) {
230 if (r == 0 && errno != ENOENT)
231 r = -errno;
232 }
233 }
234
235 return r;
236 }