chiark / gitweb /
journalctl: respect --after-cursor semantics with --follow in all cases
[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                         return log_error_errno(errno, "Failed to fstat(): %m");
144         }
145
146         /* This algorithm will keep deleting the oldest file of the
147          * user with the most coredumps until we are back in the size
148          * limits. Note that vacuuming for journal files is different,
149          * because we rely on rate-limiting of the messages there,
150          * to avoid being flooded. */
151
152         d = opendir("/var/lib/systemd/coredump");
153         if (!d) {
154                 if (errno == ENOENT)
155                         return 0;
156
157                 log_error_errno(errno, "Can't open coredump directory: %m");
158                 return -errno;
159         }
160
161         for (;;) {
162                 _cleanup_(vacuum_candidate_hasmap_freep) Hashmap *h = NULL;
163                 struct vacuum_candidate *worst = NULL;
164                 struct dirent *de;
165                 off_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("Failed to stat /var/lib/systemd/coredump/%s", 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, UINT32_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, UINT32_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                         log_error_errno(errno, "Failed to remove file %s: %m", worst->oldest_file);
260                         return -errno;
261                 } else
262                         log_info("Removed old coredump %s.", worst->oldest_file);
263         }
264
265         return 0;
266
267 fail:
268         log_error_errno(errno, "Failed to read directory: %m");
269         return -errno;
270 }