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