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