]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/journal/journal-vacuum.c
util-lib: split xattr-related calls into xattr-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
a0956174 28#include "dirent-util.h"
3ffd4af2 29#include "fd-util.h"
0284adc6
LP
30#include "journal-def.h"
31#include "journal-file.h"
3ffd4af2 32#include "journal-vacuum.h"
6bedfcbb 33#include "parse-util.h"
07630cea 34#include "string-util.h"
0284adc6 35#include "util.h"
89a5a90c 36#include "xattr-util.h"
0284adc6
LP
37
38struct vacuum_info {
6c142648 39 uint64_t usage;
0284adc6
LP
40 char *filename;
41
42 uint64_t realtime;
2e14c544 43
0284adc6
LP
44 sd_id128_t seqnum_id;
45 uint64_t seqnum;
0284adc6
LP
46 bool have_seqnum;
47};
48
49static 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
fb0951b0 75static void patch_realtime(
2e14c544 76 int fd,
fb0951b0
LP
77 const char *fn,
78 const struct stat *st,
79 unsigned long long *realtime) {
80
a7f7d1bd 81 usec_t x, crtime = 0;
fb0951b0
LP
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
2e14c544 87 assert(fd >= 0);
fb0951b0
LP
88 assert(fn);
89 assert(st);
90 assert(realtime);
91
92 x = timespec_load(&st->st_ctim);
3a43da28 93 if (x > 0 && x != USEC_INFINITY && x < *realtime)
fb0951b0
LP
94 *realtime = x;
95
96 x = timespec_load(&st->st_atim);
3a43da28 97 if (x > 0 && x != USEC_INFINITY && x < *realtime)
fb0951b0
LP
98 *realtime = x;
99
100 x = timespec_load(&st->st_mtim);
3a43da28 101 if (x > 0 && x != USEC_INFINITY && x < *realtime)
fb0951b0
LP
102 *realtime = x;
103
fb0951b0
LP
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
2e14c544 109 if (fd_getcrtime_at(fd, fn, &crtime, 0) >= 0) {
4a4d89b6 110 if (crtime < *realtime)
fb0951b0
LP
111 *realtime = crtime;
112 }
fb0951b0
LP
113}
114
9d647740 115static int journal_file_empty(int dir_fd, const char *name) {
48979861 116 _cleanup_close_ int fd;
332076b4
LP
117 struct stat st;
118 le64_t n_entries;
119 ssize_t n;
9d647740 120
7b5195e2
LP
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 }
9d647740 128
332076b4 129 if (fstat(fd, &st) < 0)
9d647740
ZJS
130 return -errno;
131
332076b4
LP
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;
9d647740 142
332076b4 143 return le64toh(n_entries) <= 0;
9d647740
ZJS
144}
145
fb0951b0
LP
146int journal_directory_vacuum(
147 const char *directory,
148 uint64_t max_use,
8580d1f7 149 uint64_t n_max_files,
fb0951b0 150 usec_t max_retention_usec,
dbd2a83f
LP
151 usec_t *oldest_usec,
152 bool verbose) {
fb0951b0 153
30cb029b 154 _cleanup_closedir_ DIR *d = NULL;
0284adc6 155 struct vacuum_info *list = NULL;
8580d1f7 156 unsigned n_list = 0, i, n_active_files = 0;
30cb029b 157 size_t n_allocated = 0;
289f910e 158 uint64_t sum = 0, freed = 0;
fb0951b0 159 usec_t retention_limit = 0;
dbd2a83f 160 char sbytes[FORMAT_BYTES_MAX];
8580d1f7
LP
161 struct dirent *de;
162 int r;
0284adc6
LP
163
164 assert(directory);
165
8580d1f7 166 if (max_use <= 0 && max_retention_usec <= 0 && n_max_files <= 0)
0284adc6
LP
167 return 0;
168
fb0951b0
LP
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
0284adc6
LP
177 d = opendir(directory);
178 if (!d)
179 return -errno;
180
8580d1f7
LP
181 FOREACH_DIRENT_ALL(de, d, r = -errno; goto finish) {
182
0284adc6 183 unsigned long long seqnum = 0, realtime;
8580d1f7 184 _cleanup_free_ char *p = NULL;
0284adc6
LP
185 sd_id128_t seqnum_id;
186 bool have_seqnum;
8580d1f7
LP
187 uint64_t size;
188 struct stat st;
189 size_t q;
0284adc6 190
8580d1f7
LP
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);
0284adc6 193 continue;
8580d1f7 194 }
0284adc6
LP
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
8580d1f7
LP
203 /* Vacuum archived files. Active files are
204 * left around */
0284adc6 205
8580d1f7
LP
206 if (q < 1 + 32 + 1 + 16 + 1 + 16 + 8) {
207 n_active_files++;
0284adc6 208 continue;
8580d1f7 209 }
0284adc6
LP
210
211 if (de->d_name[q-8-16-1] != '-' ||
212 de->d_name[q-8-16-1-16-1] != '-' ||
8580d1f7
LP
213 de->d_name[q-8-16-1-16-1-32-1] != '@') {
214 n_active_files++;
0284adc6 215 continue;
8580d1f7 216 }
0284adc6
LP
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) {
8580d1f7 226 n_active_files++;
0284adc6
LP
227 continue;
228 }
229
230 if (sscanf(de->d_name + q-8-16-1-16, "%16llx-%16llx.journal", &seqnum, &realtime) != 2) {
8580d1f7 231 n_active_files++;
0284adc6
LP
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
8580d1f7
LP
242 if (q < 1 + 16 + 1 + 16 + 8 + 1) {
243 n_active_files ++;
0284adc6 244 continue;
8580d1f7 245 }
0284adc6
LP
246
247 if (de->d_name[q-1-8-16-1] != '-' ||
8580d1f7
LP
248 de->d_name[q-1-8-16-1-16-1] != '@') {
249 n_active_files ++;
0284adc6 250 continue;
8580d1f7 251 }
0284adc6
LP
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) {
8580d1f7 260 n_active_files ++;
0284adc6
LP
261 continue;
262 }
263
264 have_seqnum = false;
8580d1f7
LP
265 } else {
266 /* We do not vacuum unknown files! */
267 log_debug("Not vacuuming unknown file %s.", de->d_name);
0284adc6 268 continue;
8580d1f7 269 }
0284adc6 270
8580d1f7 271 size = 512UL * (uint64_t) st.st_blocks;
9d647740 272
8580d1f7
LP
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. */
289f910e
ZJS
280
281 if (unlinkat(dirfd(d), p, 0) >= 0) {
8580d1f7
LP
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
289f910e
ZJS
286 freed += size;
287 } else if (errno != ENOENT)
56f64d95 288 log_warning_errno(errno, "Failed to delete empty archived journal %s/%s: %m", directory, p);
289f910e 289
9d647740
ZJS
290 continue;
291 }
292
8580d1f7 293 patch_realtime(dirfd(d), p, &st, &realtime);
fb0951b0 294
26d8ff04 295 if (!GREEDY_REALLOC(list, n_allocated, n_list + 1)) {
26d8ff04
LP
296 r = -ENOMEM;
297 goto finish;
298 }
0284adc6
LP
299
300 list[n_list].filename = p;
8580d1f7 301 list[n_list].usage = size;
0284adc6
LP
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;
0284adc6 306 n_list ++;
8580d1f7
LP
307
308 p = NULL;
309 sum += size;
0284adc6
LP
310 }
311
7ff7394d 312 qsort_safe(list, n_list, sizeof(struct vacuum_info), vacuum_compare);
0284adc6 313
fb0951b0 314 for (i = 0; i < n_list; i++) {
8580d1f7
LP
315 unsigned left;
316
317 left = n_active_files + n_list - i;
318
fb0951b0 319 if ((max_retention_usec <= 0 || list[i].realtime >= retention_limit) &&
8580d1f7
LP
320 (max_use <= 0 || sum <= max_use) &&
321 (n_max_files <= 0 || left <= n_max_files))
0284adc6
LP
322 break;
323
324 if (unlinkat(dirfd(d), list[i].filename, 0) >= 0) {
dbd2a83f 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));
289f910e 326 freed += list[i].usage;
4fa25d62 327
6c142648 328 if (list[i].usage < sum)
4fa25d62
LP
329 sum -= list[i].usage;
330 else
331 sum = 0;
332
0284adc6 333 } else if (errno != ENOENT)
56f64d95 334 log_warning_errno(errno, "Failed to delete archived journal %s/%s: %m", directory, list[i].filename);
0284adc6
LP
335 }
336
fb0951b0
LP
337 if (oldest_usec && i < n_list && (*oldest_usec == 0 || list[i].realtime < *oldest_usec))
338 *oldest_usec = list[i].realtime;
339
8580d1f7
LP
340 r = 0;
341
0284adc6
LP
342finish:
343 for (i = 0; i < n_list; i++)
344 free(list[i].filename);
0284adc6
LP
345 free(list);
346
dbd2a83f 347 log_full(verbose ? LOG_INFO : LOG_DEBUG, "Vacuuming done, freed %s of archived journals on disk.", format_bytes(sbytes, sizeof(sbytes), freed));
289f910e 348
0284adc6
LP
349 return r;
350}