]> git.proxmox.com Git - systemd.git/blame - src/journal/journal-vacuum.c
New upstream version 240
[systemd.git] / src / journal / journal-vacuum.c
CommitLineData
52ad194e 1/* SPDX-License-Identifier: LGPL-2.1+ */
663996b3 2
663996b3
MS
3#include <fcntl.h>
4#include <sys/stat.h>
663996b3 5#include <unistd.h>
663996b3 6
db2df898
MP
7#include "sd-id128.h"
8
9#include "alloc-util.h"
10#include "dirent-util.h"
11#include "fd-util.h"
98393f85 12#include "fs-util.h"
663996b3
MS
13#include "journal-def.h"
14#include "journal-file.h"
15#include "journal-vacuum.h"
db2df898
MP
16#include "parse-util.h"
17#include "string-util.h"
6e866b33 18#include "time-util.h"
663996b3 19#include "util.h"
db2df898 20#include "xattr-util.h"
663996b3
MS
21
22struct vacuum_info {
23 uint64_t usage;
24 char *filename;
25
26 uint64_t realtime;
6300502b 27
663996b3
MS
28 sd_id128_t seqnum_id;
29 uint64_t seqnum;
663996b3
MS
30 bool have_seqnum;
31};
32
6e866b33
MB
33static int vacuum_compare(const struct vacuum_info *a, const struct vacuum_info *b) {
34 int r;
663996b3
MS
35
36 if (a->have_seqnum && b->have_seqnum &&
6e866b33
MB
37 sd_id128_equal(a->seqnum_id, b->seqnum_id))
38 return CMP(a->seqnum, b->seqnum);
663996b3 39
6e866b33
MB
40 r = CMP(a->realtime, b->realtime);
41 if (r != 0)
42 return r;
43
44 if (a->have_seqnum && b->have_seqnum)
663996b3 45 return memcmp(&a->seqnum_id, &b->seqnum_id, 16);
6e866b33
MB
46
47 return strcmp(a->filename, b->filename);
663996b3
MS
48}
49
50static void patch_realtime(
6300502b 51 int fd,
663996b3
MS
52 const char *fn,
53 const struct stat *st,
54 unsigned long long *realtime) {
55
e3bff60a 56 usec_t x, crtime = 0;
663996b3
MS
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
6300502b 62 assert(fd >= 0);
663996b3
MS
63 assert(fn);
64 assert(st);
65 assert(realtime);
66
67 x = timespec_load(&st->st_ctim);
5eef597e 68 if (x > 0 && x != USEC_INFINITY && x < *realtime)
663996b3
MS
69 *realtime = x;
70
71 x = timespec_load(&st->st_atim);
5eef597e 72 if (x > 0 && x != USEC_INFINITY && x < *realtime)
663996b3
MS
73 *realtime = x;
74
75 x = timespec_load(&st->st_mtim);
5eef597e 76 if (x > 0 && x != USEC_INFINITY && x < *realtime)
663996b3
MS
77 *realtime = x;
78
663996b3
MS
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
6300502b 84 if (fd_getcrtime_at(fd, fn, &crtime, 0) >= 0) {
e735f4d4 85 if (crtime < *realtime)
663996b3
MS
86 *realtime = crtime;
87 }
663996b3
MS
88}
89
14228c0d 90static int journal_file_empty(int dir_fd, const char *name) {
14228c0d 91 _cleanup_close_ int fd;
f47781d8
MP
92 struct stat st;
93 le64_t n_entries;
94 ssize_t n;
14228c0d 95
6300502b
MP
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 }
14228c0d 103
f47781d8 104 if (fstat(fd, &st) < 0)
14228c0d
MB
105 return -errno;
106
f47781d8
MP
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;
14228c0d 117
f47781d8 118 return le64toh(n_entries) <= 0;
14228c0d
MB
119}
120
663996b3
MS
121int journal_directory_vacuum(
122 const char *directory,
123 uint64_t max_use,
6300502b 124 uint64_t n_max_files,
663996b3 125 usec_t max_retention_usec,
f47781d8
MP
126 usec_t *oldest_usec,
127 bool verbose) {
663996b3 128
6e866b33
MB
129 uint64_t sum = 0, freed = 0, n_active_files = 0;
130 size_t n_list = 0, n_allocated = 0, i;
14228c0d 131 _cleanup_closedir_ DIR *d = NULL;
663996b3 132 struct vacuum_info *list = NULL;
663996b3 133 usec_t retention_limit = 0;
f47781d8 134 char sbytes[FORMAT_BYTES_MAX];
6300502b
MP
135 struct dirent *de;
136 int r;
663996b3
MS
137
138 assert(directory);
139
6300502b 140 if (max_use <= 0 && max_retention_usec <= 0 && n_max_files <= 0)
663996b3
MS
141 return 0;
142
6e866b33
MB
143 if (max_retention_usec > 0)
144 retention_limit = usec_sub_unsigned(now(CLOCK_REALTIME), max_retention_usec);
663996b3
MS
145
146 d = opendir(directory);
147 if (!d)
148 return -errno;
149
6300502b
MP
150 FOREACH_DIRENT_ALL(de, d, r = -errno; goto finish) {
151
663996b3 152 unsigned long long seqnum = 0, realtime;
6300502b 153 _cleanup_free_ char *p = NULL;
663996b3
MS
154 sd_id128_t seqnum_id;
155 bool have_seqnum;
6300502b
MP
156 uint64_t size;
157 struct stat st;
158 size_t q;
663996b3 159
6300502b
MP
160 if (fstatat(dirfd(d), de->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0) {
161 log_debug_errno(errno, "Failed to stat file %s while vacuuming, ignoring: %m", de->d_name);
663996b3 162 continue;
6300502b 163 }
663996b3
MS
164
165 if (!S_ISREG(st.st_mode))
166 continue;
167
168 q = strlen(de->d_name);
169
170 if (endswith(de->d_name, ".journal")) {
171
6300502b
MP
172 /* Vacuum archived files. Active files are
173 * left around */
663996b3 174
6300502b
MP
175 if (q < 1 + 32 + 1 + 16 + 1 + 16 + 8) {
176 n_active_files++;
663996b3 177 continue;
6300502b 178 }
663996b3
MS
179
180 if (de->d_name[q-8-16-1] != '-' ||
181 de->d_name[q-8-16-1-16-1] != '-' ||
6300502b
MP
182 de->d_name[q-8-16-1-16-1-32-1] != '@') {
183 n_active_files++;
663996b3 184 continue;
6300502b 185 }
663996b3
MS
186
187 p = strdup(de->d_name);
188 if (!p) {
189 r = -ENOMEM;
190 goto finish;
191 }
192
193 de->d_name[q-8-16-1-16-1] = 0;
194 if (sd_id128_from_string(de->d_name + q-8-16-1-16-1-32, &seqnum_id) < 0) {
6300502b 195 n_active_files++;
663996b3
MS
196 continue;
197 }
198
199 if (sscanf(de->d_name + q-8-16-1-16, "%16llx-%16llx.journal", &seqnum, &realtime) != 2) {
6300502b 200 n_active_files++;
663996b3
MS
201 continue;
202 }
203
204 have_seqnum = true;
205
206 } else if (endswith(de->d_name, ".journal~")) {
207 unsigned long long tmp;
208
209 /* Vacuum corrupted files */
210
6300502b 211 if (q < 1 + 16 + 1 + 16 + 8 + 1) {
aa27b158 212 n_active_files++;
663996b3 213 continue;
6300502b 214 }
663996b3
MS
215
216 if (de->d_name[q-1-8-16-1] != '-' ||
6300502b 217 de->d_name[q-1-8-16-1-16-1] != '@') {
aa27b158 218 n_active_files++;
663996b3 219 continue;
6300502b 220 }
663996b3
MS
221
222 p = strdup(de->d_name);
223 if (!p) {
224 r = -ENOMEM;
225 goto finish;
226 }
227
228 if (sscanf(de->d_name + q-1-8-16-1-16, "%16llx-%16llx.journal~", &realtime, &tmp) != 2) {
aa27b158 229 n_active_files++;
663996b3
MS
230 continue;
231 }
232
233 have_seqnum = false;
6300502b
MP
234 } else {
235 /* We do not vacuum unknown files! */
236 log_debug("Not vacuuming unknown file %s.", de->d_name);
663996b3 237 continue;
6300502b 238 }
663996b3 239
6300502b 240 size = 512UL * (uint64_t) st.st_blocks;
663996b3 241
6300502b
MP
242 r = journal_file_empty(dirfd(d), p);
243 if (r < 0) {
244 log_debug_errno(r, "Failed check if %s is empty, ignoring: %m", p);
245 continue;
246 }
247 if (r > 0) {
248 /* Always vacuum empty non-online files. */
663996b3 249
98393f85
MB
250 r = unlinkat_deallocate(dirfd(d), p, 0);
251 if (r >= 0) {
6300502b
MP
252
253 log_full(verbose ? LOG_INFO : LOG_DEBUG,
254 "Deleted empty archived journal %s/%s (%s).", directory, p, format_bytes(sbytes, sizeof(sbytes), size));
255
14228c0d 256 freed += size;
98393f85
MB
257 } else if (r != -ENOENT)
258 log_warning_errno(r, "Failed to delete empty archived journal %s/%s: %m", directory, p);
663996b3 259
14228c0d 260 continue;
663996b3
MS
261 }
262
6300502b 263 patch_realtime(dirfd(d), p, &st, &realtime);
14228c0d 264
f47781d8 265 if (!GREEDY_REALLOC(list, n_allocated, n_list + 1)) {
f47781d8
MP
266 r = -ENOMEM;
267 goto finish;
268 }
14228c0d 269
6e866b33
MB
270 list[n_list++] = (struct vacuum_info) {
271 .filename = TAKE_PTR(p),
272 .usage = size,
273 .seqnum = seqnum,
274 .realtime = realtime,
275 .seqnum_id = seqnum_id,
276 .have_seqnum = have_seqnum,
277 };
6300502b 278
6300502b 279 sum += size;
663996b3
MS
280 }
281
6e866b33 282 typesafe_qsort(list, n_list, vacuum_compare);
663996b3
MS
283
284 for (i = 0; i < n_list; i++) {
6e866b33 285 uint64_t left;
6300502b
MP
286
287 left = n_active_files + n_list - i;
288
663996b3 289 if ((max_retention_usec <= 0 || list[i].realtime >= retention_limit) &&
6300502b
MP
290 (max_use <= 0 || sum <= max_use) &&
291 (n_max_files <= 0 || left <= n_max_files))
663996b3
MS
292 break;
293
98393f85
MB
294 r = unlinkat_deallocate(dirfd(d), list[i].filename, 0);
295 if (r >= 0) {
f47781d8 296 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));
14228c0d 297 freed += list[i].usage;
663996b3
MS
298
299 if (list[i].usage < sum)
300 sum -= list[i].usage;
301 else
302 sum = 0;
303
98393f85
MB
304 } else if (r != -ENOENT)
305 log_warning_errno(r, "Failed to delete archived journal %s/%s: %m", directory, list[i].filename);
663996b3
MS
306 }
307
308 if (oldest_usec && i < n_list && (*oldest_usec == 0 || list[i].realtime < *oldest_usec))
309 *oldest_usec = list[i].realtime;
310
6300502b
MP
311 r = 0;
312
663996b3
MS
313finish:
314 for (i = 0; i < n_list; i++)
315 free(list[i].filename);
663996b3
MS
316 free(list);
317
8a584da2 318 log_full(verbose ? LOG_INFO : LOG_DEBUG, "Vacuuming done, freed %s of archived journals from %s.", format_bytes(sbytes, sizeof(sbytes), freed), directory);
663996b3
MS
319
320 return r;
321}