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