chiark / gitweb /
json: fix a mem leak
[elogind.git] / src / shared / 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(subdir_fd);
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 ((flags & REMOVE_SUBVOLUME) && st.st_ino == 256) {
120
121                                 /* This could be a subvolume, try to remove it */
122
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
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
165 int 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
179         if ((flags & (REMOVE_SUBVOLUME|REMOVE_ROOT|REMOVE_PHYSICAL)) == (REMOVE_SUBVOLUME|REMOVE_ROOT|REMOVE_PHYSICAL)) {
180                 /* Try to remove as subvolume first */
181                 r = btrfs_subvol_remove(path, true);
182                 if (r >= 0)
183                         return r;
184
185                 if (r != -ENOTTY && r != -EINVAL)
186                         return r;
187
188                 /* Not btrfs or not a subvolume */
189         }
190
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) {
217                 if (rmdir(path) < 0) {
218                         if (r == 0 && errno != ENOENT)
219                                 r = -errno;
220                 }
221         }
222
223         return r;
224 }