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