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