Line data Source code
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 45 : int rm_rf_children(int fd, RemoveFlags flags, struct stat *root_dev) {
28 90 : _cleanup_closedir_ DIR *d = NULL;
29 45 : int ret = 0, r;
30 :
31 45 : 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 45 : if (!(flags & REMOVE_PHYSICAL)) {
37 :
38 0 : r = fd_is_temporary_fs(fd);
39 0 : if (r < 0) {
40 0 : safe_close(fd);
41 0 : return r;
42 : }
43 :
44 0 : 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 0 : log_error("Attempted to remove disk file system, and we can't allow that.");
52 0 : safe_close(fd);
53 0 : return -EPERM;
54 : }
55 : }
56 :
57 45 : d = fdopendir(fd);
58 45 : if (!d) {
59 0 : safe_close(fd);
60 0 : return errno == ENOENT ? 0 : -errno;
61 : }
62 :
63 : for (;;) {
64 : struct dirent *de;
65 : bool is_dir;
66 : struct stat st;
67 :
68 209 : errno = 0;
69 209 : de = readdir(d);
70 209 : if (!de) {
71 45 : if (errno != 0 && ret == 0)
72 0 : ret = -errno;
73 45 : return ret;
74 : }
75 :
76 164 : if (streq(de->d_name, ".") || streq(de->d_name, ".."))
77 180 : continue;
78 :
79 148 : if (de->d_type == DT_UNKNOWN ||
80 96 : (de->d_type == DT_DIR && (root_dev || (flags & REMOVE_SUBVOLUME)))) {
81 0 : if (fstatat(fd, de->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0) {
82 0 : if (ret == 0 && errno != ENOENT)
83 0 : ret = -errno;
84 0 : continue;
85 : }
86 :
87 0 : is_dir = S_ISDIR(st.st_mode);
88 : } else
89 74 : is_dir = de->d_type == DT_DIR;
90 :
91 74 : if (is_dir) {
92 : int subdir_fd;
93 :
94 : /* if root_dev is set, remove subdirectories only if device is same */
95 22 : if (root_dev && st.st_dev != root_dev->st_dev)
96 0 : continue;
97 :
98 22 : subdir_fd = openat(fd, de->d_name, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW|O_NOATIME);
99 22 : if (subdir_fd < 0) {
100 0 : if (ret == 0 && errno != ENOENT)
101 0 : ret = -errno;
102 0 : continue;
103 : }
104 :
105 : /* Stop at mount points */
106 22 : r = fd_is_mount_point(fd, de->d_name, 0);
107 22 : if (r < 0) {
108 0 : if (ret == 0 && r != -ENOENT)
109 0 : ret = r;
110 :
111 0 : safe_close(subdir_fd);
112 0 : continue;
113 : }
114 22 : if (r) {
115 0 : safe_close(subdir_fd);
116 0 : continue;
117 : }
118 :
119 22 : if ((flags & REMOVE_SUBVOLUME) && st.st_ino == 256) {
120 :
121 : /* This could be a subvolume, try to remove it */
122 :
123 0 : r = btrfs_subvol_remove_fd(fd, de->d_name, true);
124 0 : if (r < 0) {
125 0 : if (r != -ENOTTY && r != -EINVAL) {
126 0 : if (ret == 0)
127 0 : ret = r;
128 :
129 0 : safe_close(subdir_fd);
130 0 : 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 0 : safe_close(subdir_fd);
139 0 : 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 22 : r = rm_rf_children(subdir_fd, flags | REMOVE_PHYSICAL, root_dev);
147 22 : if (r < 0 && ret == 0)
148 0 : ret = r;
149 :
150 22 : if (unlinkat(fd, de->d_name, AT_REMOVEDIR) < 0) {
151 0 : if (ret == 0 && errno != ENOENT)
152 0 : ret = -errno;
153 : }
154 :
155 52 : } else if (!(flags & REMOVE_ONLY_DIRECTORIES)) {
156 :
157 52 : if (unlinkat(fd, de->d_name, 0) < 0) {
158 0 : if (ret == 0 && errno != ENOENT)
159 0 : ret = -errno;
160 : }
161 : }
162 164 : }
163 : }
164 :
165 79 : int rm_rf(const char *path, RemoveFlags flags) {
166 : int fd, r;
167 : struct statfs s;
168 :
169 79 : 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 79 : if (path_equal(path, "/")) {
175 0 : log_error("Attempted to remove entire root file system, and we can't allow that.");
176 0 : return -EPERM;
177 : }
178 :
179 79 : if ((flags & (REMOVE_SUBVOLUME|REMOVE_ROOT|REMOVE_PHYSICAL)) == (REMOVE_SUBVOLUME|REMOVE_ROOT|REMOVE_PHYSICAL)) {
180 : /* Try to remove as subvolume first */
181 0 : r = btrfs_subvol_remove(path, true);
182 0 : if (r >= 0)
183 0 : return r;
184 :
185 0 : if (r != -ENOTTY && r != -EINVAL && r != -ENOTDIR)
186 0 : return r;
187 :
188 : /* Not btrfs or not a subvolume */
189 : }
190 :
191 79 : fd = open(path, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW|O_NOATIME);
192 79 : if (fd < 0) {
193 :
194 56 : if (errno != ENOTDIR && errno != ELOOP)
195 51 : return -errno;
196 :
197 5 : if (!(flags & REMOVE_PHYSICAL)) {
198 0 : if (statfs(path, &s) < 0)
199 0 : return -errno;
200 :
201 0 : if (!is_temporary_fs(&s)) {
202 0 : log_error("Attempted to remove disk file system, and we can't allow that.");
203 0 : return -EPERM;
204 : }
205 : }
206 :
207 5 : if ((flags & REMOVE_ROOT) && !(flags & REMOVE_ONLY_DIRECTORIES))
208 5 : if (unlink(path) < 0 && errno != ENOENT)
209 0 : return -errno;
210 :
211 5 : return 0;
212 : }
213 :
214 23 : r = rm_rf_children(fd, flags, NULL);
215 :
216 23 : if (flags & REMOVE_ROOT) {
217 23 : if (rmdir(path) < 0) {
218 0 : if (r == 0 && errno != ENOENT)
219 0 : r = -errno;
220 : }
221 : }
222 :
223 23 : return r;
224 : }
|