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