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