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