chiark / gitweb /
rm-rf: never cross mount points
[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 || (de->d_type == DT_DIR && root_dev)) {
79                         if (fstatat(fd, de->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0) {
80                                 if (ret == 0 && errno != ENOENT)
81                                         ret = -errno;
82                                 continue;
83                         }
84
85                         is_dir = S_ISDIR(st.st_mode);
86                 } else
87                         is_dir = de->d_type == DT_DIR;
88
89                 if (is_dir) {
90                         int subdir_fd;
91
92                         /* if root_dev is set, remove subdirectories only if device is same */
93                         if (root_dev && st.st_dev != root_dev->st_dev)
94                                 continue;
95
96                         subdir_fd = openat(fd, de->d_name, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW|O_NOATIME);
97                         if (subdir_fd < 0) {
98                                 if (ret == 0 && errno != ENOENT)
99                                         ret = -errno;
100                                 continue;
101                         }
102
103                         /* Stop at mount points */
104                         r = fd_is_mount_point(subdir_fd);
105                         if (r < 0) {
106                                 if (ret == 0 && r != -ENOENT)
107                                         ret = r;
108
109                                 safe_close(subdir_fd);
110                                 continue;
111                         }
112                         if (r) {
113                                 safe_close(subdir_fd);
114                                 continue;
115                         }
116
117                         /* We pass REMOVE_PHYSICAL here, to avoid
118                          * doing the fstatfs() to check the file
119                          * system type again for each directory */
120                         r = rm_rf_children(subdir_fd, flags | REMOVE_PHYSICAL, root_dev);
121                         if (r < 0 && ret == 0)
122                                 ret = r;
123
124                         if (unlinkat(fd, de->d_name, AT_REMOVEDIR) < 0) {
125                                 if (ret == 0 && errno != ENOENT)
126                                         ret = -errno;
127                         }
128
129                 } else if (!(flags & REMOVE_ONLY_DIRECTORIES)) {
130
131                         if (unlinkat(fd, de->d_name, 0) < 0) {
132                                 if (ret == 0 && errno != ENOENT)
133                                         ret = -errno;
134                         }
135                 }
136         }
137 }
138
139 int rm_rf(const char *path, RemoveFlags flags) {
140         int fd, r;
141         struct statfs s;
142
143         assert(path);
144
145         /* We refuse to clean the root file system with this
146          * call. This is extra paranoia to never cause a really
147          * seriously broken system. */
148         if (path_equal(path, "/")) {
149                 log_error("Attempted to remove entire root file system, and we can't allow that.");
150                 return -EPERM;
151         }
152
153         fd = open(path, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW|O_NOATIME);
154         if (fd < 0) {
155
156                 if (errno != ENOTDIR && errno != ELOOP)
157                         return -errno;
158
159                 if (!(flags & REMOVE_PHYSICAL)) {
160                         if (statfs(path, &s) < 0)
161                                 return -errno;
162
163                         if (!is_temporary_fs(&s)) {
164                                 log_error("Attempted to remove disk file system, and we can't allow that.");
165                                 return -EPERM;
166                         }
167                 }
168
169                 if ((flags & REMOVE_ROOT) && !(flags & REMOVE_ONLY_DIRECTORIES))
170                         if (unlink(path) < 0 && errno != ENOENT)
171                                 return -errno;
172
173                 return 0;
174         }
175
176         r = rm_rf_children(fd, flags, NULL);
177
178         if (flags & REMOVE_ROOT) {
179                 if (rmdir(path) < 0 && errno != ENOENT) {
180                         if (r == 0)
181                                 r = -errno;
182                 }
183         }
184
185         return r;
186 }