]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/journal/journal-vacuum.c
util-lib: split out allocation calls into alloc-util.[ch]
[thirdparty/systemd.git] / src / journal / journal-vacuum.c
CommitLineData
0284adc6
LP
1/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3/***
4 This file is part of systemd.
5
6 Copyright 2011 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
0284adc6
LP
22#include <fcntl.h>
23#include <sys/stat.h>
0284adc6 24#include <unistd.h>
fb0951b0 25
07630cea
LP
26#include "sd-id128.h"
27
b5efdb8a 28#include "alloc-util.h"
a0956174 29#include "dirent-util.h"
3ffd4af2 30#include "fd-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
LP
243 if (q < 1 + 16 + 1 + 16 + 8 + 1) {
244 n_active_files ++;
0284adc6 245 continue;
8580d1f7 246 }
0284adc6
LP
247
248 if (de->d_name[q-1-8-16-1] != '-' ||
8580d1f7
LP
249 de->d_name[q-1-8-16-1-16-1] != '@') {
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) {
8580d1f7 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
ZJS
281
282 if (unlinkat(dirfd(d), p, 0) >= 0) {
8580d1f7
LP
283
284 log_full(verbose ? LOG_INFO : LOG_DEBUG,
285 "Deleted empty archived journal %s/%s (%s).", directory, p, format_bytes(sbytes, sizeof(sbytes), size));
286
289f910e
ZJS
287 freed += size;
288 } else if (errno != ENOENT)
56f64d95 289 log_warning_errno(errno, "Failed to delete empty archived journal %s/%s: %m", directory, p);
289f910e 290
9d647740
ZJS
291 continue;
292 }
293
8580d1f7 294 patch_realtime(dirfd(d), p, &st, &realtime);
fb0951b0 295
26d8ff04 296 if (!GREEDY_REALLOC(list, n_allocated, n_list + 1)) {
26d8ff04
LP
297 r = -ENOMEM;
298 goto finish;
299 }
0284adc6
LP
300
301 list[n_list].filename = p;
8580d1f7 302 list[n_list].usage = size;
0284adc6
LP
303 list[n_list].seqnum = seqnum;
304 list[n_list].realtime = realtime;
305 list[n_list].seqnum_id = seqnum_id;
306 list[n_list].have_seqnum = have_seqnum;
0284adc6 307 n_list ++;
8580d1f7
LP
308
309 p = NULL;
310 sum += size;
0284adc6
LP
311 }
312
7ff7394d 313 qsort_safe(list, n_list, sizeof(struct vacuum_info), vacuum_compare);
0284adc6 314
fb0951b0 315 for (i = 0; i < n_list; i++) {
8580d1f7
LP
316 unsigned left;
317
318 left = n_active_files + n_list - i;
319
fb0951b0 320 if ((max_retention_usec <= 0 || list[i].realtime >= retention_limit) &&
8580d1f7
LP
321 (max_use <= 0 || sum <= max_use) &&
322 (n_max_files <= 0 || left <= n_max_files))
0284adc6
LP
323 break;
324
325 if (unlinkat(dirfd(d), list[i].filename, 0) >= 0) {
dbd2a83f 326 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 327 freed += list[i].usage;
4fa25d62 328
6c142648 329 if (list[i].usage < sum)
4fa25d62
LP
330 sum -= list[i].usage;
331 else
332 sum = 0;
333
0284adc6 334 } else if (errno != ENOENT)
56f64d95 335 log_warning_errno(errno, "Failed to delete archived journal %s/%s: %m", directory, list[i].filename);
0284adc6
LP
336 }
337
fb0951b0
LP
338 if (oldest_usec && i < n_list && (*oldest_usec == 0 || list[i].realtime < *oldest_usec))
339 *oldest_usec = list[i].realtime;
340
8580d1f7
LP
341 r = 0;
342
0284adc6
LP
343finish:
344 for (i = 0; i < n_list; i++)
345 free(list[i].filename);
0284adc6
LP
346 free(list);
347
dbd2a83f 348 log_full(verbose ? LOG_INFO : LOG_DEBUG, "Vacuuming done, freed %s of archived journals on disk.", format_bytes(sbytes, sizeof(sbytes), freed));
289f910e 349
0284adc6
LP
350 return r;
351}