]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/basic/rm-rf.c
util-lib: move mount related utility calls to mount-util.[ch]
[thirdparty/systemd.git] / src / basic / 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 "btrfs-util.h"
23 #include "fd-util.h"
24 #include "mount-util.h"
25 #include "path-util.h"
26 #include "rm-rf.h"
27 #include "string-util.h"
28 #include "util.h"
29
30 int rm_rf_children(int fd, RemoveFlags flags, struct stat *root_dev) {
31 _cleanup_closedir_ DIR *d = NULL;
32 int ret = 0, r;
33
34 assert(fd >= 0);
35
36 /* This returns the first error we run into, but nevertheless
37 * tries to go on. This closes the passed fd. */
38
39 if (!(flags & REMOVE_PHYSICAL)) {
40
41 r = fd_is_temporary_fs(fd);
42 if (r < 0) {
43 safe_close(fd);
44 return r;
45 }
46
47 if (!r) {
48 /* We refuse to clean physical file systems
49 * with this call, unless explicitly
50 * requested. This is extra paranoia just to
51 * be sure we never ever remove non-state
52 * data */
53
54 log_error("Attempted to remove disk file system, and we can't allow that.");
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
66 for (;;) {
67 struct dirent *de;
68 bool is_dir;
69 struct stat st;
70
71 errno = 0;
72 de = readdir(d);
73 if (!de) {
74 if (errno != 0 && ret == 0)
75 ret = -errno;
76 return ret;
77 }
78
79 if (streq(de->d_name, ".") || streq(de->d_name, ".."))
80 continue;
81
82 if (de->d_type == DT_UNKNOWN ||
83 (de->d_type == DT_DIR && (root_dev || (flags & REMOVE_SUBVOLUME)))) {
84 if (fstatat(fd, de->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0) {
85 if (ret == 0 && errno != ENOENT)
86 ret = -errno;
87 continue;
88 }
89
90 is_dir = S_ISDIR(st.st_mode);
91 } else
92 is_dir = de->d_type == DT_DIR;
93
94 if (is_dir) {
95 int subdir_fd;
96
97 /* if root_dev is set, remove subdirectories only if device is same */
98 if (root_dev && st.st_dev != root_dev->st_dev)
99 continue;
100
101 subdir_fd = openat(fd, de->d_name, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW|O_NOATIME);
102 if (subdir_fd < 0) {
103 if (ret == 0 && errno != ENOENT)
104 ret = -errno;
105 continue;
106 }
107
108 /* Stop at mount points */
109 r = fd_is_mount_point(fd, de->d_name, 0);
110 if (r < 0) {
111 if (ret == 0 && r != -ENOENT)
112 ret = r;
113
114 safe_close(subdir_fd);
115 continue;
116 }
117 if (r) {
118 safe_close(subdir_fd);
119 continue;
120 }
121
122 if ((flags & REMOVE_SUBVOLUME) && st.st_ino == 256) {
123
124 /* This could be a subvolume, try to remove it */
125
126 r = btrfs_subvol_remove_fd(fd, de->d_name, BTRFS_REMOVE_RECURSIVE|BTRFS_REMOVE_QUOTA);
127 if (r < 0) {
128 if (r != -ENOTTY && r != -EINVAL) {
129 if (ret == 0)
130 ret = r;
131
132 safe_close(subdir_fd);
133 continue;
134 }
135
136 /* ENOTTY, then it wasn't a
137 * btrfs subvolume, continue
138 * below. */
139 } else {
140 /* It was a subvolume, continue. */
141 safe_close(subdir_fd);
142 continue;
143 }
144 }
145
146 /* We pass REMOVE_PHYSICAL here, to avoid
147 * doing the fstatfs() to check the file
148 * system type again for each directory */
149 r = rm_rf_children(subdir_fd, flags | REMOVE_PHYSICAL, root_dev);
150 if (r < 0 && ret == 0)
151 ret = r;
152
153 if (unlinkat(fd, de->d_name, AT_REMOVEDIR) < 0) {
154 if (ret == 0 && errno != ENOENT)
155 ret = -errno;
156 }
157
158 } else if (!(flags & REMOVE_ONLY_DIRECTORIES)) {
159
160 if (unlinkat(fd, de->d_name, 0) < 0) {
161 if (ret == 0 && errno != ENOENT)
162 ret = -errno;
163 }
164 }
165 }
166 }
167
168 int rm_rf(const char *path, RemoveFlags flags) {
169 int fd, r;
170 struct statfs s;
171
172 assert(path);
173
174 /* We refuse to clean the root file system with this
175 * call. This is extra paranoia to never cause a really
176 * seriously broken system. */
177 if (path_equal(path, "/")) {
178 log_error("Attempted to remove entire root file system, and we can't allow that.");
179 return -EPERM;
180 }
181
182 if ((flags & (REMOVE_SUBVOLUME|REMOVE_ROOT|REMOVE_PHYSICAL)) == (REMOVE_SUBVOLUME|REMOVE_ROOT|REMOVE_PHYSICAL)) {
183 /* Try to remove as subvolume first */
184 r = btrfs_subvol_remove(path, BTRFS_REMOVE_RECURSIVE|BTRFS_REMOVE_QUOTA);
185 if (r >= 0)
186 return r;
187
188 if (r != -ENOTTY && r != -EINVAL && r != -ENOTDIR)
189 return r;
190
191 /* Not btrfs or not a subvolume */
192 }
193
194 fd = open(path, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW|O_NOATIME);
195 if (fd < 0) {
196
197 if (errno != ENOTDIR && errno != ELOOP)
198 return -errno;
199
200 if (!(flags & REMOVE_PHYSICAL)) {
201 if (statfs(path, &s) < 0)
202 return -errno;
203
204 if (!is_temporary_fs(&s)) {
205 log_error("Attempted to remove disk file system, and we can't allow that.");
206 return -EPERM;
207 }
208 }
209
210 if ((flags & REMOVE_ROOT) && !(flags & REMOVE_ONLY_DIRECTORIES))
211 if (unlink(path) < 0 && errno != ENOENT)
212 return -errno;
213
214 return 0;
215 }
216
217 r = rm_rf_children(fd, flags, NULL);
218
219 if (flags & REMOVE_ROOT) {
220 if (rmdir(path) < 0) {
221 if (r == 0 && errno != ENOENT)
222 r = -errno;
223 }
224 }
225
226 return r;
227 }