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