1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3 #include <sys/statvfs.h>
4 #include <sys/stat.h>
5 #include <sys/types.h>
6 #include <unistd.h>
7
8 #include "alloc-util.h"
9 #include "coredump-vacuum.h"
10 #include "dirent-util.h"
11 #include "fd-util.h"
12 #include "fs-util.h"
13 #include "hashmap.h"
14 #include "macro.h"
15 #include "memory-util.h"
16 #include "stat-util.h"
17 #include "string-util.h"
18 #include "time-util.h"
19 #include "user-util.h"
20
21 #define DEFAULT_MAX_USE_LOWER (uint64_t) (1ULL*1024ULL*1024ULL) /* 1 MiB */
22 #define DEFAULT_MAX_USE_UPPER (uint64_t) (4ULL*1024ULL*1024ULL*1024ULL) /* 4 GiB */
23 #define DEFAULT_KEEP_FREE_UPPER (uint64_t) (4ULL*1024ULL*1024ULL*1024ULL) /* 4 GiB */
24 #define DEFAULT_KEEP_FREE (uint64_t) (1024ULL*1024ULL) /* 1 MB */
25
26 typedef struct VacuumCandidate {
27 unsigned n_files;
28 char *oldest_file;
29 usec_t oldest_mtime;
30 } VacuumCandidate;
31
vacuum_candidate_free(VacuumCandidate * c)32 static VacuumCandidate* vacuum_candidate_free(VacuumCandidate *c) {
33 if (!c)
34 return NULL;
35
36 free(c->oldest_file);
37 return mfree(c);
38 }
39 DEFINE_TRIVIAL_CLEANUP_FUNC(VacuumCandidate*, vacuum_candidate_free);
40
vacuum_candidate_hashmap_free(Hashmap * h)41 static Hashmap* vacuum_candidate_hashmap_free(Hashmap *h) {
42 return hashmap_free_with_destructor(h, vacuum_candidate_free);
43 }
44
45 DEFINE_TRIVIAL_CLEANUP_FUNC(Hashmap*, vacuum_candidate_hashmap_free);
46
uid_from_file_name(const char * filename,uid_t * uid)47 static int uid_from_file_name(const char *filename, uid_t *uid) {
48 const char *p, *e, *u;
49
50 p = startswith(filename, "core.");
51 if (!p)
52 return -EINVAL;
53
54 /* Skip the comm field */
55 p = strchr(p, '.');
56 if (!p)
57 return -EINVAL;
58 p++;
59
60 /* Find end up UID */
61 e = strchr(p, '.');
62 if (!e)
63 return -EINVAL;
64
65 u = strndupa_safe(p, e - p);
66 return parse_uid(u, uid);
67 }
68
vacuum_necessary(int fd,uint64_t sum,uint64_t keep_free,uint64_t max_use)69 static bool vacuum_necessary(int fd, uint64_t sum, uint64_t keep_free, uint64_t max_use) {
70 uint64_t fs_size = 0, fs_free = UINT64_MAX;
71 struct statvfs sv;
72
73 assert(fd >= 0);
74
75 if (fstatvfs(fd, &sv) >= 0) {
76 fs_size = sv.f_frsize * sv.f_blocks;
77 fs_free = sv.f_frsize * sv.f_bfree;
78 }
79
80 if (max_use == UINT64_MAX) {
81
82 if (fs_size > 0) {
83 max_use = PAGE_ALIGN(fs_size / 10); /* 10% */
84
85 if (max_use > DEFAULT_MAX_USE_UPPER)
86 max_use = DEFAULT_MAX_USE_UPPER;
87
88 if (max_use < DEFAULT_MAX_USE_LOWER)
89 max_use = DEFAULT_MAX_USE_LOWER;
90 } else
91 max_use = DEFAULT_MAX_USE_LOWER;
92 } else
93 max_use = PAGE_ALIGN(max_use);
94
95 if (max_use > 0 && sum > max_use)
96 return true;
97
98 if (keep_free == UINT64_MAX) {
99
100 if (fs_size > 0) {
101 keep_free = PAGE_ALIGN((fs_size * 3) / 20); /* 15% */
102
103 if (keep_free > DEFAULT_KEEP_FREE_UPPER)
104 keep_free = DEFAULT_KEEP_FREE_UPPER;
105 } else
106 keep_free = DEFAULT_KEEP_FREE;
107 } else
108 keep_free = PAGE_ALIGN(keep_free);
109
110 if (keep_free > 0 && fs_free < keep_free)
111 return true;
112
113 return false;
114 }
115
coredump_vacuum(int exclude_fd,uint64_t keep_free,uint64_t max_use)116 int coredump_vacuum(int exclude_fd, uint64_t keep_free, uint64_t max_use) {
117 _cleanup_closedir_ DIR *d = NULL;
118 struct stat exclude_st;
119 int r;
120
121 if (keep_free == 0 && max_use == 0)
122 return 0;
123
124 if (exclude_fd >= 0) {
125 if (fstat(exclude_fd, &exclude_st) < 0)
126 return log_error_errno(errno, "Failed to fstat(): %m");
127 }
128
129 /* This algorithm will keep deleting the oldest file of the
130 * user with the most coredumps until we are back in the size
131 * limits. Note that vacuuming for journal files is different,
132 * because we rely on rate-limiting of the messages there,
133 * to avoid being flooded. */
134
135 d = opendir("/var/lib/systemd/coredump");
136 if (!d) {
137 if (errno == ENOENT)
138 return 0;
139
140 return log_error_errno(errno, "Can't open coredump directory: %m");
141 }
142
143 for (;;) {
144 _cleanup_(vacuum_candidate_hashmap_freep) Hashmap *h = NULL;
145 VacuumCandidate *worst = NULL;
146 uint64_t sum = 0;
147
148 rewinddir(d);
149
150 FOREACH_DIRENT(de, d, goto fail) {
151 VacuumCandidate *c;
152 struct stat st;
153 uid_t uid;
154 usec_t t;
155
156 r = uid_from_file_name(de->d_name, &uid);
157 if (r < 0)
158 continue;
159
160 if (fstatat(dirfd(d), de->d_name, &st, AT_NO_AUTOMOUNT|AT_SYMLINK_NOFOLLOW) < 0) {
161 if (errno == ENOENT)
162 continue;
163
164 log_warning_errno(errno, "Failed to stat /var/lib/systemd/coredump/%s: %m", de->d_name);
165 continue;
166 }
167
168 if (!S_ISREG(st.st_mode))
169 continue;
170
171 if (exclude_fd >= 0 && stat_inode_same(&exclude_st, &st))
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) VacuumCandidate *n = NULL;
197
198 n = new0(VacuumCandidate, 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 }
246