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