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