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