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