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