]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/basic/rm-rf.c
util: split out escaping code into escape.[ch]
[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
22#include "util.h"
23#include "path-util.h"
d9e2daaf 24#include "btrfs-util.h"
c6878637
LP
25#include "rm-rf.h"
26
27int 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
9e9b663a
LP
79 if (de->d_type == DT_UNKNOWN ||
80 (de->d_type == DT_DIR && (root_dev || (flags & REMOVE_SUBVOLUME)))) {
c6878637
LP
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
f25afeb6 94 /* if root_dev is set, remove subdirectories only if device is same */
c6878637
LP
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
f25afeb6 105 /* Stop at mount points */
5d409034 106 r = fd_is_mount_point(fd, de->d_name, 0);
f25afeb6
LP
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
9e9b663a 119 if ((flags & REMOVE_SUBVOLUME) && st.st_ino == 256) {
9e9b663a
LP
120
121 /* This could be a subvolume, try to remove it */
122
5bcd08db 123 r = btrfs_subvol_remove_fd(fd, de->d_name, BTRFS_REMOVE_RECURSIVE|BTRFS_REMOVE_QUOTA);
d9e2daaf
LP
124 if (r < 0) {
125 if (r != -ENOTTY && r != -EINVAL) {
9e9b663a 126 if (ret == 0)
d9e2daaf 127 ret = r;
9e9b663a
LP
128
129 safe_close(subdir_fd);
130 continue;
131 }
132
d9e2daaf
LP
133 /* ENOTTY, then it wasn't a
134 * btrfs subvolume, continue
135 * below. */
9e9b663a
LP
136 } else {
137 /* It was a subvolume, continue. */
138 safe_close(subdir_fd);
139 continue;
140 }
141 }
142
c6878637
LP
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
165int 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
d9e2daaf
LP
179 if ((flags & (REMOVE_SUBVOLUME|REMOVE_ROOT|REMOVE_PHYSICAL)) == (REMOVE_SUBVOLUME|REMOVE_ROOT|REMOVE_PHYSICAL)) {
180 /* Try to remove as subvolume first */
5bcd08db 181 r = btrfs_subvol_remove(path, BTRFS_REMOVE_RECURSIVE|BTRFS_REMOVE_QUOTA);
d9e2daaf
LP
182 if (r >= 0)
183 return r;
184
636aabc2 185 if (r != -ENOTTY && r != -EINVAL && r != -ENOTDIR)
d9e2daaf
LP
186 return r;
187
188 /* Not btrfs or not a subvolume */
189 }
190
c6878637
LP
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) {
d9e2daaf
LP
217 if (rmdir(path) < 0) {
218 if (r == 0 && errno != ENOENT)
c6878637
LP
219 r = -errno;
220 }
221 }
222
223 return r;
224}