chiark / gitweb /
util: make malloc0 ask calloc for one block of size n
[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 "util.h"
23 #include "path-util.h"
24 // #include "btrfs-util.h"
25 #include "rm-rf.h"
26
27 int 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
79                 if (de->d_type == DT_UNKNOWN ||
80                     (de->d_type == DT_DIR && (root_dev || (flags & REMOVE_SUBVOLUME)))) {
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
94                         /* if root_dev is set, remove subdirectories only if device is same */
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
105                         /* Stop at mount points */
106                         r = fd_is_mount_point(fd, de->d_name, 0);
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
119 #if 0
120                         if ((flags & REMOVE_SUBVOLUME) && st.st_ino == 256) {
121
122                                 /* This could be a subvolume, try to remove it */
123                                 r = btrfs_subvol_remove_fd(fd, de->d_name, true);
124                                 if (r < 0) {
125                                         if (r != -ENOTTY && r != -EINVAL) {
126                                                 if (ret == 0)
127                                                         ret = r;
128
129                                                 safe_close(subdir_fd);
130                                                 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                                         safe_close(subdir_fd);
139                                         continue;
140                                 }
141                         }
142 #endif // 0
143
144                         /* We pass REMOVE_PHYSICAL here, to avoid
145                          * doing the fstatfs() to check the file
146                          * system type again for each directory */
147                         r = rm_rf_children(subdir_fd, flags | REMOVE_PHYSICAL, root_dev);
148                         if (r < 0 && ret == 0)
149                                 ret = r;
150
151                         if (unlinkat(fd, de->d_name, AT_REMOVEDIR) < 0) {
152                                 if (ret == 0 && errno != ENOENT)
153                                         ret = -errno;
154                         }
155
156                 } else if (!(flags & REMOVE_ONLY_DIRECTORIES)) {
157
158                         if (unlinkat(fd, de->d_name, 0) < 0) {
159                                 if (ret == 0 && errno != ENOENT)
160                                         ret = -errno;
161                         }
162                 }
163         }
164 }
165
166 int rm_rf(const char *path, RemoveFlags flags) {
167         int fd, r;
168         struct statfs s;
169
170         assert(path);
171
172         /* We refuse to clean the root file system with this
173          * call. This is extra paranoia to never cause a really
174          * seriously broken system. */
175         if (path_equal(path, "/")) {
176                 log_error("Attempted to remove entire root file system, and we can't allow that.");
177                 return -EPERM;
178         }
179
180 #if 0
181         if ((flags & (REMOVE_SUBVOLUME|REMOVE_ROOT|REMOVE_PHYSICAL)) == (REMOVE_SUBVOLUME|REMOVE_ROOT|REMOVE_PHYSICAL)) {
182                 /* Try to remove as subvolume first */
183                 r = btrfs_subvol_remove(path, true);
184                 if (r >= 0)
185                         return r;
186
187                 if (r != -ENOTTY && r != -EINVAL && r != -ENOTDIR)
188                         return r;
189                 /* Not btrfs or not a subvolume */
190         }
191 #endif // 0
192
193         fd = open(path, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW|O_NOATIME);
194         if (fd < 0) {
195
196                 if (errno != ENOTDIR && errno != ELOOP)
197                         return -errno;
198
199                 if (!(flags & REMOVE_PHYSICAL)) {
200                         if (statfs(path, &s) < 0)
201                                 return -errno;
202
203                         if (!is_temporary_fs(&s)) {
204                                 log_error("Attempted to remove disk file system, and we can't allow that.");
205                                 return -EPERM;
206                         }
207                 }
208
209                 if ((flags & REMOVE_ROOT) && !(flags & REMOVE_ONLY_DIRECTORIES))
210                         if (unlink(path) < 0 && errno != ENOENT)
211                                 return -errno;
212
213                 return 0;
214         }
215
216         r = rm_rf_children(fd, flags, NULL);
217
218         if (flags & REMOVE_ROOT) {
219                 if (rmdir(path) < 0) {
220                         if (r == 0 && errno != ENOENT)
221                                 r = -errno;
222                 }
223         }
224
225         return r;
226 }