Branch data Line data Source code
1 : : /* SPDX-License-Identifier: LGPL-2.1+ */
2 : :
3 : : #include <sys/statvfs.h>
4 : : #include <sys/stat.h>
5 : : #include <sys/types.h>
6 : : #include <unistd.h>
7 : :
8 : : #include "alloc-util.h"
9 : : #include "coredump-vacuum.h"
10 : : #include "dirent-util.h"
11 : : #include "fd-util.h"
12 : : #include "fs-util.h"
13 : : #include "hashmap.h"
14 : : #include "macro.h"
15 : : #include "memory-util.h"
16 : : #include "string-util.h"
17 : : #include "time-util.h"
18 : : #include "user-util.h"
19 : :
20 : : #define DEFAULT_MAX_USE_LOWER (uint64_t) (1ULL*1024ULL*1024ULL) /* 1 MiB */
21 : : #define DEFAULT_MAX_USE_UPPER (uint64_t) (4ULL*1024ULL*1024ULL*1024ULL) /* 4 GiB */
22 : : #define DEFAULT_KEEP_FREE_UPPER (uint64_t) (4ULL*1024ULL*1024ULL*1024ULL) /* 4 GiB */
23 : : #define DEFAULT_KEEP_FREE (uint64_t) (1024ULL*1024ULL) /* 1 MB */
24 : :
25 : : struct vacuum_candidate {
26 : : unsigned n_files;
27 : : char *oldest_file;
28 : : usec_t oldest_mtime;
29 : : };
30 : :
31 : 0 : static void vacuum_candidate_free(struct vacuum_candidate *c) {
32 [ # # ]: 0 : if (!c)
33 : 0 : return;
34 : :
35 : 0 : free(c->oldest_file);
36 : 0 : free(c);
37 : : }
38 : :
39 [ # # ]: 0 : DEFINE_TRIVIAL_CLEANUP_FUNC(struct vacuum_candidate*, vacuum_candidate_free);
40 : :
41 : 0 : static void vacuum_candidate_hashmap_free(Hashmap *h) {
42 [ # # ]: 0 : hashmap_free_with_destructor(h, vacuum_candidate_free);
43 : 0 : }
44 : :
45 [ # # ]: 0 : DEFINE_TRIVIAL_CLEANUP_FUNC(Hashmap*, vacuum_candidate_hashmap_free);
46 : :
47 : 0 : static int uid_from_file_name(const char *filename, uid_t *uid) {
48 : : const char *p, *e, *u;
49 : :
50 : 0 : p = startswith(filename, "core.");
51 [ # # ]: 0 : if (!p)
52 : 0 : return -EINVAL;
53 : :
54 : : /* Skip the comm field */
55 : 0 : p = strchr(p, '.');
56 [ # # ]: 0 : if (!p)
57 : 0 : return -EINVAL;
58 : 0 : p++;
59 : :
60 : : /* Find end up UID */
61 : 0 : e = strchr(p, '.');
62 [ # # ]: 0 : if (!e)
63 : 0 : return -EINVAL;
64 : :
65 : 0 : u = strndupa(p, e-p);
66 : 0 : return parse_uid(u, uid);
67 : : }
68 : :
69 : 0 : static bool vacuum_necessary(int fd, uint64_t sum, uint64_t keep_free, uint64_t max_use) {
70 : 0 : uint64_t fs_size = 0, fs_free = (uint64_t) -1;
71 : : struct statvfs sv;
72 : :
73 [ # # ]: 0 : assert(fd >= 0);
74 : :
75 [ # # ]: 0 : if (fstatvfs(fd, &sv) >= 0) {
76 : 0 : fs_size = sv.f_frsize * sv.f_blocks;
77 : 0 : fs_free = sv.f_frsize * sv.f_bfree;
78 : : }
79 : :
80 [ # # ]: 0 : if (max_use == (uint64_t) -1) {
81 : :
82 [ # # ]: 0 : if (fs_size > 0) {
83 : 0 : max_use = PAGE_ALIGN(fs_size / 10); /* 10% */
84 : :
85 [ # # ]: 0 : if (max_use > DEFAULT_MAX_USE_UPPER)
86 : 0 : max_use = DEFAULT_MAX_USE_UPPER;
87 : :
88 [ # # ]: 0 : if (max_use < DEFAULT_MAX_USE_LOWER)
89 : 0 : max_use = DEFAULT_MAX_USE_LOWER;
90 : : } else
91 : 0 : max_use = DEFAULT_MAX_USE_LOWER;
92 : : } else
93 : 0 : max_use = PAGE_ALIGN(max_use);
94 : :
95 [ # # # # ]: 0 : if (max_use > 0 && sum > max_use)
96 : 0 : return true;
97 : :
98 [ # # ]: 0 : if (keep_free == (uint64_t) -1) {
99 : :
100 [ # # ]: 0 : if (fs_size > 0) {
101 : 0 : keep_free = PAGE_ALIGN((fs_size * 3) / 20); /* 15% */
102 : :
103 [ # # ]: 0 : if (keep_free > DEFAULT_KEEP_FREE_UPPER)
104 : 0 : keep_free = DEFAULT_KEEP_FREE_UPPER;
105 : : } else
106 : 0 : keep_free = DEFAULT_KEEP_FREE;
107 : : } else
108 : 0 : keep_free = PAGE_ALIGN(keep_free);
109 : :
110 [ # # # # ]: 0 : if (keep_free > 0 && fs_free < keep_free)
111 : 0 : return true;
112 : :
113 : 0 : return false;
114 : : }
115 : :
116 : 0 : int coredump_vacuum(int exclude_fd, uint64_t keep_free, uint64_t max_use) {
117 : 0 : _cleanup_closedir_ DIR *d = NULL;
118 : : struct stat exclude_st;
119 : : int r;
120 : :
121 [ # # # # ]: 0 : if (keep_free == 0 && max_use == 0)
122 : 0 : return 0;
123 : :
124 [ # # ]: 0 : if (exclude_fd >= 0) {
125 [ # # ]: 0 : if (fstat(exclude_fd, &exclude_st) < 0)
126 [ # # ]: 0 : return log_error_errno(errno, "Failed to fstat(): %m");
127 : : }
128 : :
129 : : /* This algorithm will keep deleting the oldest file of the
130 : : * user with the most coredumps until we are back in the size
131 : : * limits. Note that vacuuming for journal files is different,
132 : : * because we rely on rate-limiting of the messages there,
133 : : * to avoid being flooded. */
134 : :
135 : 0 : d = opendir("/var/lib/systemd/coredump");
136 [ # # ]: 0 : if (!d) {
137 [ # # ]: 0 : if (errno == ENOENT)
138 : 0 : return 0;
139 : :
140 [ # # ]: 0 : return log_error_errno(errno, "Can't open coredump directory: %m");
141 : : }
142 : :
143 : 0 : for (;;) {
144 [ # # # # : 0 : _cleanup_(vacuum_candidate_hashmap_freep) Hashmap *h = NULL;
# ]
145 : 0 : struct vacuum_candidate *worst = NULL;
146 : : struct dirent *de;
147 : 0 : uint64_t sum = 0;
148 : :
149 : 0 : rewinddir(d);
150 : :
151 [ # # # # : 0 : FOREACH_DIRENT(de, d, goto fail) {
# # ]
152 : : struct vacuum_candidate *c;
153 : : struct stat st;
154 : : uid_t uid;
155 : : usec_t t;
156 : :
157 : 0 : r = uid_from_file_name(de->d_name, &uid);
158 [ # # ]: 0 : if (r < 0)
159 : 0 : continue;
160 : :
161 [ # # ]: 0 : if (fstatat(dirfd(d), de->d_name, &st, AT_NO_AUTOMOUNT|AT_SYMLINK_NOFOLLOW) < 0) {
162 [ # # ]: 0 : if (errno == ENOENT)
163 : 0 : continue;
164 : :
165 [ # # ]: 0 : log_warning_errno(errno, "Failed to stat /var/lib/systemd/coredump/%s: %m", de->d_name);
166 : 0 : continue;
167 : : }
168 : :
169 [ # # ]: 0 : if (!S_ISREG(st.st_mode))
170 : 0 : continue;
171 : :
172 [ # # ]: 0 : if (exclude_fd >= 0 &&
173 [ # # ]: 0 : exclude_st.st_dev == st.st_dev &&
174 [ # # ]: 0 : exclude_st.st_ino == st.st_ino)
175 : 0 : continue;
176 : :
177 : 0 : r = hashmap_ensure_allocated(&h, NULL);
178 [ # # ]: 0 : if (r < 0)
179 : 0 : return log_oom();
180 : :
181 : 0 : t = timespec_load(&st.st_mtim);
182 : :
183 : 0 : c = hashmap_get(h, UID_TO_PTR(uid));
184 [ # # ]: 0 : if (c) {
185 : :
186 [ # # ]: 0 : if (t < c->oldest_mtime) {
187 : : char *n;
188 : :
189 : 0 : n = strdup(de->d_name);
190 [ # # ]: 0 : if (!n)
191 : 0 : return log_oom();
192 : :
193 : 0 : free(c->oldest_file);
194 : 0 : c->oldest_file = n;
195 : 0 : c->oldest_mtime = t;
196 : : }
197 : :
198 : : } else {
199 [ # # ]: 0 : _cleanup_(vacuum_candidate_freep) struct vacuum_candidate *n = NULL;
200 : :
201 : 0 : n = new0(struct vacuum_candidate, 1);
202 [ # # ]: 0 : if (!n)
203 : 0 : return log_oom();
204 : :
205 : 0 : n->oldest_file = strdup(de->d_name);
206 [ # # ]: 0 : if (!n->oldest_file)
207 : 0 : return log_oom();
208 : :
209 : 0 : n->oldest_mtime = t;
210 : :
211 : 0 : r = hashmap_put(h, UID_TO_PTR(uid), n);
212 [ # # ]: 0 : if (r < 0)
213 : 0 : return log_oom();
214 : :
215 : 0 : c = TAKE_PTR(n);
216 : : }
217 : :
218 : 0 : c->n_files++;
219 : :
220 [ # # ]: 0 : if (!worst ||
221 [ # # ]: 0 : worst->n_files < c->n_files ||
222 [ # # # # ]: 0 : (worst->n_files == c->n_files && c->oldest_mtime < worst->oldest_mtime))
223 : 0 : worst = c;
224 : :
225 : 0 : sum += st.st_blocks * 512;
226 : : }
227 : :
228 [ # # ]: 0 : if (!worst)
229 : 0 : break;
230 : :
231 : 0 : r = vacuum_necessary(dirfd(d), sum, keep_free, max_use);
232 [ # # ]: 0 : if (r <= 0)
233 : 0 : return r;
234 : :
235 : 0 : r = unlinkat_deallocate(dirfd(d), worst->oldest_file, 0);
236 [ # # ]: 0 : if (r == -ENOENT)
237 : 0 : continue;
238 [ # # ]: 0 : if (r < 0)
239 [ # # ]: 0 : return log_error_errno(r, "Failed to remove file %s: %m", worst->oldest_file);
240 : :
241 [ # # ]: 0 : log_info("Removed old coredump %s.", worst->oldest_file);
242 : : }
243 : :
244 : 0 : return 0;
245 : :
246 : 0 : fail:
247 [ # # ]: 0 : return log_error_errno(errno, "Failed to read directory: %m");
248 : : }
|