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