]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/journal/journal-vacuum.c
coredump,journal: when vacuuming use new unlinkat_deallocate() calls
[thirdparty/systemd.git] / src / journal / journal-vacuum.c
CommitLineData
53e1b683 1/* SPDX-License-Identifier: LGPL-2.1+ */
0284adc6
LP
2/***
3 This file is part of systemd.
4
5 Copyright 2011 Lennart Poettering
6
7 systemd is free software; you can redistribute it and/or modify it
8 under the terms of the GNU Lesser General Public License as published by
9 the Free Software Foundation; either version 2.1 of the License, or
10 (at your option) any later version.
11
12 systemd is distributed in the hope that it will be useful, but
13 WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 Lesser General Public License for more details.
16
17 You should have received a copy of the GNU Lesser General Public License
18 along with systemd; If not, see <http://www.gnu.org/licenses/>.
19***/
20
0284adc6
LP
21#include <fcntl.h>
22#include <sys/stat.h>
0284adc6 23#include <unistd.h>
fb0951b0 24
07630cea
LP
25#include "sd-id128.h"
26
b5efdb8a 27#include "alloc-util.h"
a0956174 28#include "dirent-util.h"
3ffd4af2 29#include "fd-util.h"
47c073aa 30#include "fs-util.h"
0284adc6
LP
31#include "journal-def.h"
32#include "journal-file.h"
3ffd4af2 33#include "journal-vacuum.h"
6bedfcbb 34#include "parse-util.h"
07630cea 35#include "string-util.h"
0284adc6 36#include "util.h"
89a5a90c 37#include "xattr-util.h"
0284adc6
LP
38
39struct vacuum_info {
6c142648 40 uint64_t usage;
0284adc6
LP
41 char *filename;
42
43 uint64_t realtime;
2e14c544 44
0284adc6
LP
45 sd_id128_t seqnum_id;
46 uint64_t seqnum;
0284adc6
LP
47 bool have_seqnum;
48};
49
50static int vacuum_compare(const void *_a, const void *_b) {
51 const struct vacuum_info *a, *b;
52
53 a = _a;
54 b = _b;
55
56 if (a->have_seqnum && b->have_seqnum &&
57 sd_id128_equal(a->seqnum_id, b->seqnum_id)) {
58 if (a->seqnum < b->seqnum)
59 return -1;
60 else if (a->seqnum > b->seqnum)
61 return 1;
62 else
63 return 0;
64 }
65
66 if (a->realtime < b->realtime)
67 return -1;
68 else if (a->realtime > b->realtime)
69 return 1;
70 else if (a->have_seqnum && b->have_seqnum)
71 return memcmp(&a->seqnum_id, &b->seqnum_id, 16);
72 else
73 return strcmp(a->filename, b->filename);
74}
75
fb0951b0 76static void patch_realtime(
2e14c544 77 int fd,
fb0951b0
LP
78 const char *fn,
79 const struct stat *st,
80 unsigned long long *realtime) {
81
a7f7d1bd 82 usec_t x, crtime = 0;
fb0951b0
LP
83
84 /* The timestamp was determined by the file name, but let's
85 * see if the file might actually be older than the file name
86 * suggested... */
87
2e14c544 88 assert(fd >= 0);
fb0951b0
LP
89 assert(fn);
90 assert(st);
91 assert(realtime);
92
93 x = timespec_load(&st->st_ctim);
3a43da28 94 if (x > 0 && x != USEC_INFINITY && x < *realtime)
fb0951b0
LP
95 *realtime = x;
96
97 x = timespec_load(&st->st_atim);
3a43da28 98 if (x > 0 && x != USEC_INFINITY && x < *realtime)
fb0951b0
LP
99 *realtime = x;
100
101 x = timespec_load(&st->st_mtim);
3a43da28 102 if (x > 0 && x != USEC_INFINITY && x < *realtime)
fb0951b0
LP
103 *realtime = x;
104
fb0951b0
LP
105 /* Let's read the original creation time, if possible. Ideally
106 * we'd just query the creation time the FS might provide, but
107 * unfortunately there's currently no sane API to query
108 * it. Hence let's implement this manually... */
109
2e14c544 110 if (fd_getcrtime_at(fd, fn, &crtime, 0) >= 0) {
4a4d89b6 111 if (crtime < *realtime)
fb0951b0
LP
112 *realtime = crtime;
113 }
fb0951b0
LP
114}
115
9d647740 116static int journal_file_empty(int dir_fd, const char *name) {
48979861 117 _cleanup_close_ int fd;
332076b4
LP
118 struct stat st;
119 le64_t n_entries;
120 ssize_t n;
9d647740 121
7b5195e2
LP
122 fd = openat(dir_fd, name, O_RDONLY|O_CLOEXEC|O_NOFOLLOW|O_NONBLOCK|O_NOATIME);
123 if (fd < 0) {
124 /* Maybe failed due to O_NOATIME and lack of privileges? */
125 fd = openat(dir_fd, name, O_RDONLY|O_CLOEXEC|O_NOFOLLOW|O_NONBLOCK);
126 if (fd < 0)
127 return -errno;
128 }
9d647740 129
332076b4 130 if (fstat(fd, &st) < 0)
9d647740
ZJS
131 return -errno;
132
332076b4
LP
133 /* If an offline file doesn't even have a header we consider it empty */
134 if (st.st_size < (off_t) sizeof(Header))
135 return 1;
136
137 /* If the number of entries is empty, we consider it empty, too */
138 n = pread(fd, &n_entries, sizeof(n_entries), offsetof(Header, n_entries));
139 if (n < 0)
140 return -errno;
141 if (n != sizeof(n_entries))
142 return -EIO;
9d647740 143
332076b4 144 return le64toh(n_entries) <= 0;
9d647740
ZJS
145}
146
fb0951b0
LP
147int journal_directory_vacuum(
148 const char *directory,
149 uint64_t max_use,
8580d1f7 150 uint64_t n_max_files,
fb0951b0 151 usec_t max_retention_usec,
dbd2a83f
LP
152 usec_t *oldest_usec,
153 bool verbose) {
fb0951b0 154
30cb029b 155 _cleanup_closedir_ DIR *d = NULL;
0284adc6 156 struct vacuum_info *list = NULL;
8580d1f7 157 unsigned n_list = 0, i, n_active_files = 0;
30cb029b 158 size_t n_allocated = 0;
289f910e 159 uint64_t sum = 0, freed = 0;
fb0951b0 160 usec_t retention_limit = 0;
dbd2a83f 161 char sbytes[FORMAT_BYTES_MAX];
8580d1f7
LP
162 struct dirent *de;
163 int r;
0284adc6
LP
164
165 assert(directory);
166
8580d1f7 167 if (max_use <= 0 && max_retention_usec <= 0 && n_max_files <= 0)
0284adc6
LP
168 return 0;
169
fb0951b0
LP
170 if (max_retention_usec > 0) {
171 retention_limit = now(CLOCK_REALTIME);
172 if (retention_limit > max_retention_usec)
173 retention_limit -= max_retention_usec;
174 else
175 max_retention_usec = retention_limit = 0;
176 }
177
0284adc6
LP
178 d = opendir(directory);
179 if (!d)
180 return -errno;
181
8580d1f7
LP
182 FOREACH_DIRENT_ALL(de, d, r = -errno; goto finish) {
183
0284adc6 184 unsigned long long seqnum = 0, realtime;
8580d1f7 185 _cleanup_free_ char *p = NULL;
0284adc6
LP
186 sd_id128_t seqnum_id;
187 bool have_seqnum;
8580d1f7
LP
188 uint64_t size;
189 struct stat st;
190 size_t q;
0284adc6 191
8580d1f7
LP
192 if (fstatat(dirfd(d), de->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0) {
193 log_debug_errno(errno, "Failed to stat file %s while vacuuming, ignoring: %m", de->d_name);
0284adc6 194 continue;
8580d1f7 195 }
0284adc6
LP
196
197 if (!S_ISREG(st.st_mode))
198 continue;
199
200 q = strlen(de->d_name);
201
202 if (endswith(de->d_name, ".journal")) {
203
8580d1f7
LP
204 /* Vacuum archived files. Active files are
205 * left around */
0284adc6 206
8580d1f7
LP
207 if (q < 1 + 32 + 1 + 16 + 1 + 16 + 8) {
208 n_active_files++;
0284adc6 209 continue;
8580d1f7 210 }
0284adc6
LP
211
212 if (de->d_name[q-8-16-1] != '-' ||
213 de->d_name[q-8-16-1-16-1] != '-' ||
8580d1f7
LP
214 de->d_name[q-8-16-1-16-1-32-1] != '@') {
215 n_active_files++;
0284adc6 216 continue;
8580d1f7 217 }
0284adc6
LP
218
219 p = strdup(de->d_name);
220 if (!p) {
221 r = -ENOMEM;
222 goto finish;
223 }
224
225 de->d_name[q-8-16-1-16-1] = 0;
226 if (sd_id128_from_string(de->d_name + q-8-16-1-16-1-32, &seqnum_id) < 0) {
8580d1f7 227 n_active_files++;
0284adc6
LP
228 continue;
229 }
230
231 if (sscanf(de->d_name + q-8-16-1-16, "%16llx-%16llx.journal", &seqnum, &realtime) != 2) {
8580d1f7 232 n_active_files++;
0284adc6
LP
233 continue;
234 }
235
236 have_seqnum = true;
237
238 } else if (endswith(de->d_name, ".journal~")) {
239 unsigned long long tmp;
240
241 /* Vacuum corrupted files */
242
8580d1f7 243 if (q < 1 + 16 + 1 + 16 + 8 + 1) {
313cefa1 244 n_active_files++;
0284adc6 245 continue;
8580d1f7 246 }
0284adc6
LP
247
248 if (de->d_name[q-1-8-16-1] != '-' ||
8580d1f7 249 de->d_name[q-1-8-16-1-16-1] != '@') {
313cefa1 250 n_active_files++;
0284adc6 251 continue;
8580d1f7 252 }
0284adc6
LP
253
254 p = strdup(de->d_name);
255 if (!p) {
256 r = -ENOMEM;
257 goto finish;
258 }
259
260 if (sscanf(de->d_name + q-1-8-16-1-16, "%16llx-%16llx.journal~", &realtime, &tmp) != 2) {
313cefa1 261 n_active_files++;
0284adc6
LP
262 continue;
263 }
264
265 have_seqnum = false;
8580d1f7
LP
266 } else {
267 /* We do not vacuum unknown files! */
268 log_debug("Not vacuuming unknown file %s.", de->d_name);
0284adc6 269 continue;
8580d1f7 270 }
0284adc6 271
8580d1f7 272 size = 512UL * (uint64_t) st.st_blocks;
9d647740 273
8580d1f7
LP
274 r = journal_file_empty(dirfd(d), p);
275 if (r < 0) {
276 log_debug_errno(r, "Failed check if %s is empty, ignoring: %m", p);
277 continue;
278 }
279 if (r > 0) {
280 /* Always vacuum empty non-online files. */
289f910e 281
47c073aa
LP
282 r = unlinkat_deallocate(dirfd(d), p, 0);
283 if (r >= 0) {
8580d1f7
LP
284
285 log_full(verbose ? LOG_INFO : LOG_DEBUG,
286 "Deleted empty archived journal %s/%s (%s).", directory, p, format_bytes(sbytes, sizeof(sbytes), size));
287
289f910e 288 freed += size;
47c073aa
LP
289 } else if (r != -ENOENT)
290 log_warning_errno(r, "Failed to delete empty archived journal %s/%s: %m", directory, p);
289f910e 291
9d647740
ZJS
292 continue;
293 }
294
8580d1f7 295 patch_realtime(dirfd(d), p, &st, &realtime);
fb0951b0 296
26d8ff04 297 if (!GREEDY_REALLOC(list, n_allocated, n_list + 1)) {
26d8ff04
LP
298 r = -ENOMEM;
299 goto finish;
300 }
0284adc6
LP
301
302 list[n_list].filename = p;
8580d1f7 303 list[n_list].usage = size;
0284adc6
LP
304 list[n_list].seqnum = seqnum;
305 list[n_list].realtime = realtime;
306 list[n_list].seqnum_id = seqnum_id;
307 list[n_list].have_seqnum = have_seqnum;
313cefa1 308 n_list++;
8580d1f7
LP
309
310 p = NULL;
311 sum += size;
0284adc6
LP
312 }
313
7ff7394d 314 qsort_safe(list, n_list, sizeof(struct vacuum_info), vacuum_compare);
0284adc6 315
fb0951b0 316 for (i = 0; i < n_list; i++) {
8580d1f7
LP
317 unsigned left;
318
319 left = n_active_files + n_list - i;
320
fb0951b0 321 if ((max_retention_usec <= 0 || list[i].realtime >= retention_limit) &&
8580d1f7
LP
322 (max_use <= 0 || sum <= max_use) &&
323 (n_max_files <= 0 || left <= n_max_files))
0284adc6
LP
324 break;
325
47c073aa
LP
326 r = unlinkat_deallocate(dirfd(d), list[i].filename, 0);
327 if (r >= 0) {
dbd2a83f 328 log_full(verbose ? LOG_INFO : LOG_DEBUG, "Deleted archived journal %s/%s (%s).", directory, list[i].filename, format_bytes(sbytes, sizeof(sbytes), list[i].usage));
289f910e 329 freed += list[i].usage;
4fa25d62 330
6c142648 331 if (list[i].usage < sum)
4fa25d62
LP
332 sum -= list[i].usage;
333 else
334 sum = 0;
335
47c073aa
LP
336 } else if (r != -ENOENT)
337 log_warning_errno(r, "Failed to delete archived journal %s/%s: %m", directory, list[i].filename);
0284adc6
LP
338 }
339
fb0951b0
LP
340 if (oldest_usec && i < n_list && (*oldest_usec == 0 || list[i].realtime < *oldest_usec))
341 *oldest_usec = list[i].realtime;
342
8580d1f7
LP
343 r = 0;
344
0284adc6
LP
345finish:
346 for (i = 0; i < n_list; i++)
347 free(list[i].filename);
0284adc6
LP
348 free(list);
349
3cc44bf9 350 log_full(verbose ? LOG_INFO : LOG_DEBUG, "Vacuuming done, freed %s of archived journals from %s.", format_bytes(sbytes, sizeof(sbytes), freed), directory);
289f910e 351
0284adc6
LP
352 return r;
353}