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