chiark / gitweb /
util: rework rm_rf() logic
[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 as dir */
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                         /* We pass REMOVE_PHYSICAL here, to avoid
104                          * doing the fstatfs() to check the file
105                          * system type again for each directory */
106                         r = rm_rf_children(subdir_fd, flags | REMOVE_PHYSICAL, root_dev);
107                         if (r < 0 && ret == 0)
108                                 ret = r;
109
110                         if (unlinkat(fd, de->d_name, AT_REMOVEDIR) < 0) {
111                                 if (ret == 0 && errno != ENOENT)
112                                         ret = -errno;
113                         }
114
115                 } else if (!(flags & REMOVE_ONLY_DIRECTORIES)) {
116
117                         if (unlinkat(fd, de->d_name, 0) < 0) {
118                                 if (ret == 0 && errno != ENOENT)
119                                         ret = -errno;
120                         }
121                 }
122         }
123 }
124
125 int rm_rf(const char *path, RemoveFlags flags) {
126         int fd, r;
127         struct statfs s;
128
129         assert(path);
130
131         /* We refuse to clean the root file system with this
132          * call. This is extra paranoia to never cause a really
133          * seriously broken system. */
134         if (path_equal(path, "/")) {
135                 log_error("Attempted to remove entire root file system, and we can't allow that.");
136                 return -EPERM;
137         }
138
139         fd = open(path, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW|O_NOATIME);
140         if (fd < 0) {
141
142                 if (errno != ENOTDIR && errno != ELOOP)
143                         return -errno;
144
145                 if (!(flags & REMOVE_PHYSICAL)) {
146                         if (statfs(path, &s) < 0)
147                                 return -errno;
148
149                         if (!is_temporary_fs(&s)) {
150                                 log_error("Attempted to remove disk file system, and we can't allow that.");
151                                 return -EPERM;
152                         }
153                 }
154
155                 if ((flags & REMOVE_ROOT) && !(flags & REMOVE_ONLY_DIRECTORIES))
156                         if (unlink(path) < 0 && errno != ENOENT)
157                                 return -errno;
158
159                 return 0;
160         }
161
162         r = rm_rf_children(fd, flags, NULL);
163
164         if (flags & REMOVE_ROOT) {
165
166                 if (rmdir(path) < 0 && errno != ENOENT) {
167                         if (r == 0)
168                                 r = -errno;
169                 }
170         }
171
172         return r;
173 }