Coverage Report

Created: 2019-06-19 13:33

/src/systemd/src/basic/rm-rf.c
Line
Count
Source (jump to first uncovered line)
1
/* SPDX-License-Identifier: LGPL-2.1+ */
2
3
#include <errno.h>
4
#include <fcntl.h>
5
#include <stdbool.h>
6
#include <stddef.h>
7
#include <sys/stat.h>
8
#include <sys/statfs.h>
9
#include <unistd.h>
10
11
#include "alloc-util.h"
12
#include "btrfs-util.h"
13
#include "cgroup-util.h"
14
#include "dirent-util.h"
15
#include "fd-util.h"
16
#include "log.h"
17
#include "macro.h"
18
#include "mountpoint-util.h"
19
#include "path-util.h"
20
#include "rm-rf.h"
21
#include "stat-util.h"
22
#include "string-util.h"
23
24
0
static bool is_physical_fs(const struct statfs *sfs) {
25
0
        return !is_temporary_fs(sfs) && !is_cgroup_fs(sfs);
26
0
}
27
28
16.1k
int rm_rf_children(int fd, RemoveFlags flags, struct stat *root_dev) {
29
16.1k
        _cleanup_closedir_ DIR *d = NULL;
30
16.1k
        struct dirent *de;
31
16.1k
        int ret = 0, r;
32
16.1k
        struct statfs sfs;
33
16.1k
34
16.1k
        assert(fd >= 0);
35
16.1k
36
16.1k
        /* This returns the first error we run into, but nevertheless tries to go on. This closes the passed
37
16.1k
         * fd, in all cases, including on failure.. */
38
16.1k
39
16.1k
        if (!(flags & REMOVE_PHYSICAL)) {
40
0
41
0
                r = fstatfs(fd, &sfs);
42
0
                if (r < 0) {
43
0
                        safe_close(fd);
44
0
                        return -errno;
45
0
                }
46
0
47
0
                if (is_physical_fs(&sfs)) {
48
0
                        /* We refuse to clean physical file systems with this call,
49
0
                         * unless explicitly requested. This is extra paranoia just
50
0
                         * to be sure we never ever remove non-state data. */
51
0
                        _cleanup_free_ char *path = NULL;
52
0
53
0
                        (void) fd_get_path(fd, &path);
54
0
                        log_error("Attempted to remove disk file system under \"%s\", and we can't allow that.",
55
0
                                  strna(path));
56
0
57
0
                        safe_close(fd);
58
0
                        return -EPERM;
59
0
                }
60
16.1k
        }
61
16.1k
62
16.1k
        d = fdopendir(fd);
63
16.1k
        if (!d) {
64
0
                safe_close(fd);
65
0
                return errno == ENOENT ? 0 : -errno;
66
0
        }
67
16.1k
68
64.4k
        FOREACH_DIRENT_ALL(de, d, return -errno) {
69
48.3k
                bool is_dir;
70
48.3k
                struct stat st;
71
48.3k
72
48.3k
                if (dot_or_dot_dot(de->d_name))
73
32.2k
                        continue;
74
16.1k
75
16.1k
                if (de->d_type == DT_UNKNOWN ||
76
16.1k
                    (de->d_type == DT_DIR && (root_dev || (flags & REMOVE_SUBVOLUME)))) {
77
0
                        if (fstatat(fd, de->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0) {
78
0
                                if (ret == 0 && errno != ENOENT)
79
0
                                        ret = -errno;
80
0
                                continue;
81
0
                        }
82
0
83
0
                        is_dir = S_ISDIR(st.st_mode);
84
0
                } else
85
16.1k
                        is_dir = de->d_type == DT_DIR;
86
16.1k
87
16.1k
                if (is_dir) {
88
10.7k
                        _cleanup_close_ int subdir_fd = -1;
89
10.7k
90
10.7k
                        /* if root_dev is set, remove subdirectories only if device is same */
91
10.7k
                        if (root_dev && st.st_dev != root_dev->st_dev)
92
0
                                continue;
93
10.7k
94
10.7k
                        subdir_fd = openat(fd, de->d_name, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW|O_NOATIME);
95
10.7k
                        if (subdir_fd < 0) {
96
0
                                if (ret == 0 && errno != ENOENT)
97
0
                                        ret = -errno;
98
0
                                continue;
99
0
                        }
100
10.7k
101
10.7k
                        /* Stop at mount points */
102
10.7k
                        r = fd_is_mount_point(fd, de->d_name, 0);
103
10.7k
                        if (r < 0) {
104
0
                                if (ret == 0 && r != -ENOENT)
105
0
                                        ret = r;
106
0
107
0
                                continue;
108
0
                        }
109
10.7k
                        if (r > 0)
110
0
                                continue;
111
10.7k
112
10.7k
                        if ((flags & REMOVE_SUBVOLUME) && st.st_ino == 256) {
113
0
114
0
                                /* This could be a subvolume, try to remove it */
115
0
116
0
                                r = btrfs_subvol_remove_fd(fd, de->d_name, BTRFS_REMOVE_RECURSIVE|BTRFS_REMOVE_QUOTA);
117
0
                                if (r < 0) {
118
0
                                        if (!IN_SET(r, -ENOTTY, -EINVAL)) {
119
0
                                                if (ret == 0)
120
0
                                                        ret = r;
121
0
122
0
                                                continue;
123
0
                                        }
124
0
125
0
                                        /* ENOTTY, then it wasn't a btrfs subvolume, continue below. */
126
0
                                } else
127
0
                                        /* It was a subvolume, continue. */
128
0
                                        continue;
129
10.7k
                        }
130
10.7k
131
10.7k
                        /* We pass REMOVE_PHYSICAL here, to avoid doing the fstatfs() to check the file
132
10.7k
                         * system type again for each directory */
133
10.7k
                        r = rm_rf_children(TAKE_FD(subdir_fd), flags | REMOVE_PHYSICAL, root_dev);
134
10.7k
                        if (r < 0 && ret == 0)
135
0
                                ret = r;
136
10.7k
137
10.7k
                        if (unlinkat(fd, de->d_name, AT_REMOVEDIR) < 0) {
138
0
                                if (ret == 0 && errno != ENOENT)
139
0
                                        ret = -errno;
140
0
                        }
141
10.7k
142
10.7k
                } else if (!(flags & REMOVE_ONLY_DIRECTORIES)) {
143
5.37k
144
5.37k
                        if (unlinkat(fd, de->d_name, 0) < 0) {
145
0
                                if (ret == 0 && errno != ENOENT)
146
0
                                        ret = -errno;
147
0
                        }
148
5.37k
                }
149
16.1k
        }
150
16.1k
        return ret;
151
16.1k
}
152
153
5.37k
int rm_rf(const char *path, RemoveFlags flags) {
154
5.37k
        int fd, r;
155
5.37k
        struct statfs s;
156
5.37k
157
5.37k
        assert(path);
158
5.37k
159
5.37k
        /* For now, don't support dropping subvols when also only dropping directories, since we can't do
160
5.37k
         * this race-freely. */
161
5.37k
        if (FLAGS_SET(flags, REMOVE_ONLY_DIRECTORIES|REMOVE_SUBVOLUME))
162
5.37k
                return -EINVAL;
163
5.37k
164
5.37k
        /* We refuse to clean the root file system with this
165
5.37k
         * call. This is extra paranoia to never cause a really
166
5.37k
         * seriously broken system. */
167
5.37k
        if (path_equal_or_files_same(path, "/", AT_SYMLINK_NOFOLLOW))
168
0
                return log_error_errno(SYNTHETIC_ERRNO(EPERM),
169
5.37k
                                       "Attempted to remove entire root file system (\"%s\"), and we can't allow that.",
170
5.37k
                                       path);
171
5.37k
172
5.37k
        if (FLAGS_SET(flags, REMOVE_SUBVOLUME | REMOVE_ROOT | REMOVE_PHYSICAL)) {
173
0
                /* Try to remove as subvolume first */
174
0
                r = btrfs_subvol_remove(path, BTRFS_REMOVE_RECURSIVE|BTRFS_REMOVE_QUOTA);
175
0
                if (r >= 0)
176
0
                        return r;
177
0
178
0
                if (!IN_SET(r, -ENOTTY, -EINVAL, -ENOTDIR))
179
0
                        return r;
180
5.37k
181
5.37k
                /* Not btrfs or not a subvolume */
182
5.37k
        }
183
5.37k
184
5.37k
        fd = open(path, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW|O_NOATIME);
185
5.37k
        if (fd < 0) {
186
0
                if (!IN_SET(errno, ENOTDIR, ELOOP))
187
0
                        return -errno;
188
0
189
0
                if (!(flags & REMOVE_PHYSICAL)) {
190
0
                        if (statfs(path, &s) < 0)
191
0
                                return -errno;
192
0
193
0
                        if (is_physical_fs(&s))
194
0
                                return log_error_errno(SYNTHETIC_ERRNO(EPERM),
195
0
                                                       "Attempted to remove files from a disk file system under \"%s\", refusing.",
196
0
                                                       path);
197
0
                }
198
0
199
0
                if ((flags & REMOVE_ROOT) && !(flags & REMOVE_ONLY_DIRECTORIES))
200
0
                        if (unlink(path) < 0 && errno != ENOENT)
201
0
                                return -errno;
202
0
203
0
                return 0;
204
0
        }
205
5.37k
206
5.37k
        r = rm_rf_children(fd, flags, NULL);
207
5.37k
208
5.37k
        if (flags & REMOVE_ROOT) {
209
5.37k
                if (rmdir(path) < 0) {
210
0
                        if (r == 0 && errno != ENOENT)
211
0
                                r = -errno;
212
0
                }
213
5.37k
        }
214
5.37k
215
5.37k
        return r;
216
5.37k
}