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