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