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