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