]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/coredump/coredump-vacuum.c
license: LGPL-2.1+ -> LGPL-2.1-or-later
[thirdparty/systemd.git] / src / coredump / coredump-vacuum.c
CommitLineData
db9ecf05 1/* SPDX-License-Identifier: LGPL-2.1-or-later */
0dc5d23c
LP
2
3#include <sys/statvfs.h>
ca78ad1d
ZJS
4#include <sys/stat.h>
5#include <sys/types.h>
6#include <unistd.h>
0dc5d23c 7
b5efdb8a 8#include "alloc-util.h"
3ffd4af2 9#include "coredump-vacuum.h"
a0956174 10#include "dirent-util.h"
3ffd4af2 11#include "fd-util.h"
47c073aa 12#include "fs-util.h"
0dc5d23c
LP
13#include "hashmap.h"
14#include "macro.h"
0a970718 15#include "memory-util.h"
07630cea
LP
16#include "string-util.h"
17#include "time-util.h"
b1d4f8e1 18#include "user-util.h"
0dc5d23c 19
59f448cf
LP
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 */
0dc5d23c
LP
24
25struct vacuum_candidate {
26 unsigned n_files;
27 char *oldest_file;
28 usec_t oldest_mtime;
29};
30
31static void vacuum_candidate_free(struct vacuum_candidate *c) {
32 if (!c)
33 return;
34
35 free(c->oldest_file);
36 free(c);
37}
38
39DEFINE_TRIVIAL_CLEANUP_FUNC(struct vacuum_candidate*, vacuum_candidate_free);
40
39ab16e3
ZJS
41static void vacuum_candidate_hashmap_free(Hashmap *h) {
42 hashmap_free_with_destructor(h, vacuum_candidate_free);
0dc5d23c
LP
43}
44
39ab16e3 45DEFINE_TRIVIAL_CLEANUP_FUNC(Hashmap*, vacuum_candidate_hashmap_free);
0dc5d23c
LP
46
47static int uid_from_file_name(const char *filename, uid_t *uid) {
48 const char *p, *e, *u;
49
50 p = startswith(filename, "core.");
51 if (!p)
52 return -EINVAL;
53
54 /* Skip the comm field */
55 p = strchr(p, '.');
56 if (!p)
57 return -EINVAL;
58 p++;
59
60 /* Find end up UID */
61 e = strchr(p, '.');
62 if (!e)
63 return -EINVAL;
64
65 u = strndupa(p, e-p);
66 return parse_uid(u, uid);
67}
68
59f448cf
LP
69static bool vacuum_necessary(int fd, uint64_t sum, uint64_t keep_free, uint64_t max_use) {
70 uint64_t fs_size = 0, fs_free = (uint64_t) -1;
0dc5d23c
LP
71 struct statvfs sv;
72
73 assert(fd >= 0);
74
75 if (fstatvfs(fd, &sv) >= 0) {
76 fs_size = sv.f_frsize * sv.f_blocks;
77 fs_free = sv.f_frsize * sv.f_bfree;
78 }
79
59f448cf 80 if (max_use == (uint64_t) -1) {
0dc5d23c
LP
81
82 if (fs_size > 0) {
83 max_use = PAGE_ALIGN(fs_size / 10); /* 10% */
84
85 if (max_use > DEFAULT_MAX_USE_UPPER)
86 max_use = DEFAULT_MAX_USE_UPPER;
87
88 if (max_use < DEFAULT_MAX_USE_LOWER)
89 max_use = DEFAULT_MAX_USE_LOWER;
5470c03b 90 } else
cd4ba18a 91 max_use = DEFAULT_MAX_USE_LOWER;
0dc5d23c
LP
92 } else
93 max_use = PAGE_ALIGN(max_use);
94
95 if (max_use > 0 && sum > max_use)
96 return true;
97
59f448cf 98 if (keep_free == (uint64_t) -1) {
0dc5d23c
LP
99
100 if (fs_size > 0) {
101 keep_free = PAGE_ALIGN((fs_size * 3) / 20); /* 15% */
102
103 if (keep_free > DEFAULT_KEEP_FREE_UPPER)
104 keep_free = DEFAULT_KEEP_FREE_UPPER;
105 } else
106 keep_free = DEFAULT_KEEP_FREE;
107 } else
108 keep_free = PAGE_ALIGN(keep_free);
109
110 if (keep_free > 0 && fs_free < keep_free)
111 return true;
112
113 return false;
114}
115
59f448cf 116int coredump_vacuum(int exclude_fd, uint64_t keep_free, uint64_t max_use) {
0dc5d23c
LP
117 _cleanup_closedir_ DIR *d = NULL;
118 struct stat exclude_st;
119 int r;
120
5470c03b 121 if (keep_free == 0 && max_use == 0)
0dc5d23c
LP
122 return 0;
123
124 if (exclude_fd >= 0) {
4a62c710
MS
125 if (fstat(exclude_fd, &exclude_st) < 0)
126 return log_error_errno(errno, "Failed to fstat(): %m");
0dc5d23c
LP
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 d = opendir("/var/lib/systemd/coredump");
136 if (!d) {
137 if (errno == ENOENT)
138 return 0;
139
c292d9e9 140 return log_error_errno(errno, "Can't open coredump directory: %m");
0dc5d23c
LP
141 }
142
143 for (;;) {
39ab16e3 144 _cleanup_(vacuum_candidate_hashmap_freep) Hashmap *h = NULL;
0dc5d23c
LP
145 struct vacuum_candidate *worst = NULL;
146 struct dirent *de;
59f448cf 147 uint64_t sum = 0;
0dc5d23c
LP
148
149 rewinddir(d);
150
151 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 r = uid_from_file_name(de->d_name, &uid);
158 if (r < 0)
159 continue;
160
161 if (fstatat(dirfd(d), de->d_name, &st, AT_NO_AUTOMOUNT|AT_SYMLINK_NOFOLLOW) < 0) {
162 if (errno == ENOENT)
163 continue;
164
c292d9e9 165 log_warning_errno(errno, "Failed to stat /var/lib/systemd/coredump/%s: %m", de->d_name);
0dc5d23c
LP
166 continue;
167 }
168
169 if (!S_ISREG(st.st_mode))
170 continue;
171
172 if (exclude_fd >= 0 &&
173 exclude_st.st_dev == st.st_dev &&
174 exclude_st.st_ino == st.st_ino)
175 continue;
176
d5099efc 177 r = hashmap_ensure_allocated(&h, NULL);
0dc5d23c
LP
178 if (r < 0)
179 return log_oom();
180
181 t = timespec_load(&st.st_mtim);
182
4a0b58c4 183 c = hashmap_get(h, UID_TO_PTR(uid));
0dc5d23c
LP
184 if (c) {
185
186 if (t < c->oldest_mtime) {
187 char *n;
188
189 n = strdup(de->d_name);
190 if (!n)
191 return log_oom();
192
193 free(c->oldest_file);
194 c->oldest_file = n;
195 c->oldest_mtime = t;
196 }
197
198 } else {
199 _cleanup_(vacuum_candidate_freep) struct vacuum_candidate *n = NULL;
200
201 n = new0(struct vacuum_candidate, 1);
202 if (!n)
203 return log_oom();
204
205 n->oldest_file = strdup(de->d_name);
206 if (!n->oldest_file)
207 return log_oom();
208
209 n->oldest_mtime = t;
210
4a0b58c4 211 r = hashmap_put(h, UID_TO_PTR(uid), n);
0dc5d23c
LP
212 if (r < 0)
213 return log_oom();
214
1cc6c93a 215 c = TAKE_PTR(n);
0dc5d23c
LP
216 }
217
218 c->n_files++;
219
220 if (!worst ||
221 worst->n_files < c->n_files ||
222 (worst->n_files == c->n_files && c->oldest_mtime < worst->oldest_mtime))
223 worst = c;
224
225 sum += st.st_blocks * 512;
226 }
227
228 if (!worst)
229 break;
230
231 r = vacuum_necessary(dirfd(d), sum, keep_free, max_use);
232 if (r <= 0)
233 return r;
234
47c073aa
LP
235 r = unlinkat_deallocate(dirfd(d), worst->oldest_file, 0);
236 if (r == -ENOENT)
237 continue;
238 if (r < 0)
239 return log_error_errno(r, "Failed to remove file %s: %m", worst->oldest_file);
0dc5d23c 240
47c073aa 241 log_info("Removed old coredump %s.", worst->oldest_file);
0dc5d23c
LP
242 }
243
244 return 0;
245
246fail:
c292d9e9 247 return log_error_errno(errno, "Failed to read directory: %m");
0dc5d23c 248}