]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/shared/rm-rf.c
util: rework rm_rf() logic
[thirdparty/systemd.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 }