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