]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/journal/journal-vacuum.c
journalctl: add new --vacuum-size= and --vacuum-time= commands to clean up journal...
[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
22#include <sys/types.h>
23#include <fcntl.h>
24#include <sys/stat.h>
25#include <sys/statvfs.h>
26#include <unistd.h>
d2edfae0 27#include <sys/xattr.h>
fb0951b0 28
0284adc6
LP
29#include "journal-def.h"
30#include "journal-file.h"
31#include "journal-vacuum.h"
32#include "sd-id128.h"
33#include "util.h"
34
35struct vacuum_info {
6c142648 36 uint64_t usage;
0284adc6
LP
37 char *filename;
38
39 uint64_t realtime;
40 sd_id128_t seqnum_id;
41 uint64_t seqnum;
42
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
LP
72static void patch_realtime(
73 const char *dir,
74 const char *fn,
75 const struct stat *st,
76 unsigned long long *realtime) {
77
78 usec_t x;
fb0951b0
LP
79 uint64_t crtime;
80 _cleanup_free_ const char *path = NULL;
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
86 assert(dir);
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
108 /* Unfortunately there is is not fgetxattrat(), so we need to
109 * go via path here. :-( */
110
111 path = strjoin(dir, "/", fn, NULL);
112 if (!path)
113 return;
114
115 if (getxattr(path, "user.crtime_usec", &crtime, sizeof(crtime)) == sizeof(crtime)) {
116 crtime = le64toh(crtime);
117
118 if (crtime > 0 && crtime != (uint64_t) -1 && crtime < *realtime)
119 *realtime = crtime;
120 }
fb0951b0
LP
121}
122
9d647740 123static int journal_file_empty(int dir_fd, const char *name) {
48979861 124 int r;
9d647740 125 le64_t n_entries;
48979861 126 _cleanup_close_ int fd;
9d647740
ZJS
127
128 fd = openat(dir_fd, name, O_RDONLY|O_CLOEXEC|O_NOFOLLOW|O_NONBLOCK);
129 if (fd < 0)
130 return -errno;
131
132 if (lseek(fd, offsetof(Header, n_entries), SEEK_SET) < 0)
133 return -errno;
134
135 r = read(fd, &n_entries, sizeof(n_entries));
136 if (r != sizeof(n_entries))
137 return r == 0 ? -EINVAL : -errno;
138
139 return le64toh(n_entries) == 0;
140}
141
fb0951b0
LP
142int journal_directory_vacuum(
143 const char *directory,
144 uint64_t max_use,
fb0951b0 145 usec_t max_retention_usec,
dbd2a83f
LP
146 usec_t *oldest_usec,
147 bool verbose) {
fb0951b0 148
30cb029b 149 _cleanup_closedir_ DIR *d = NULL;
0284adc6
LP
150 int r = 0;
151 struct vacuum_info *list = NULL;
30cb029b
ZJS
152 unsigned n_list = 0, i;
153 size_t n_allocated = 0;
289f910e 154 uint64_t sum = 0, freed = 0;
fb0951b0 155 usec_t retention_limit = 0;
dbd2a83f 156 char sbytes[FORMAT_BYTES_MAX];
0284adc6
LP
157
158 assert(directory);
159
348ced90 160 if (max_use <= 0 && max_retention_usec <= 0)
0284adc6
LP
161 return 0;
162
fb0951b0
LP
163 if (max_retention_usec > 0) {
164 retention_limit = now(CLOCK_REALTIME);
165 if (retention_limit > max_retention_usec)
166 retention_limit -= max_retention_usec;
167 else
168 max_retention_usec = retention_limit = 0;
169 }
170
0284adc6
LP
171 d = opendir(directory);
172 if (!d)
173 return -errno;
174
175 for (;;) {
7d5e9c0f 176 struct dirent *de;
0284adc6
LP
177 size_t q;
178 struct stat st;
179 char *p;
180 unsigned long long seqnum = 0, realtime;
181 sd_id128_t seqnum_id;
182 bool have_seqnum;
183
78a5d04d
FW
184 errno = 0;
185 de = readdir(d);
186 if (!de && errno != 0) {
187 r = -errno;
0284adc6
LP
188 goto finish;
189 }
190
191 if (!de)
192 break;
193
194 if (fstatat(dirfd(d), de->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0)
195 continue;
196
197 if (!S_ISREG(st.st_mode))
198 continue;
199
200 q = strlen(de->d_name);
201
202 if (endswith(de->d_name, ".journal")) {
203
204 /* Vacuum archived files */
205
206 if (q < 1 + 32 + 1 + 16 + 1 + 16 + 8)
207 continue;
208
209 if (de->d_name[q-8-16-1] != '-' ||
210 de->d_name[q-8-16-1-16-1] != '-' ||
211 de->d_name[q-8-16-1-16-1-32-1] != '@')
212 continue;
213
214 p = strdup(de->d_name);
215 if (!p) {
216 r = -ENOMEM;
217 goto finish;
218 }
219
220 de->d_name[q-8-16-1-16-1] = 0;
221 if (sd_id128_from_string(de->d_name + q-8-16-1-16-1-32, &seqnum_id) < 0) {
222 free(p);
223 continue;
224 }
225
226 if (sscanf(de->d_name + q-8-16-1-16, "%16llx-%16llx.journal", &seqnum, &realtime) != 2) {
227 free(p);
228 continue;
229 }
230
231 have_seqnum = true;
232
233 } else if (endswith(de->d_name, ".journal~")) {
234 unsigned long long tmp;
235
236 /* Vacuum corrupted files */
237
238 if (q < 1 + 16 + 1 + 16 + 8 + 1)
239 continue;
240
241 if (de->d_name[q-1-8-16-1] != '-' ||
242 de->d_name[q-1-8-16-1-16-1] != '@')
243 continue;
244
245 p = strdup(de->d_name);
246 if (!p) {
247 r = -ENOMEM;
248 goto finish;
249 }
250
251 if (sscanf(de->d_name + q-1-8-16-1-16, "%16llx-%16llx.journal~", &realtime, &tmp) != 2) {
252 free(p);
253 continue;
254 }
255
256 have_seqnum = false;
257 } else
4fa25d62 258 /* We do not vacuum active files or unknown files! */
0284adc6
LP
259 continue;
260
629bfc5a 261 if (journal_file_empty(dirfd(d), p)) {
9d647740
ZJS
262 /* Always vacuum empty non-online files. */
263
289f910e
ZJS
264 uint64_t size = 512UL * (uint64_t) st.st_blocks;
265
266 if (unlinkat(dirfd(d), p, 0) >= 0) {
dbd2a83f 267 log_full(verbose ? LOG_INFO : LOG_DEBUG, "Deleted empty archived journal %s/%s (%s).", directory, p, format_bytes(sbytes, sizeof(sbytes), size));
289f910e
ZJS
268 freed += size;
269 } else if (errno != ENOENT)
dbd2a83f 270 log_warning("Failed to delete empty archived journal %s/%s: %m", directory, p);
289f910e 271
2ee0591d 272 free(p);
9d647740
ZJS
273 continue;
274 }
275
629bfc5a 276 patch_realtime(directory, p, &st, &realtime);
fb0951b0 277
30cb029b 278 GREEDY_REALLOC(list, n_allocated, n_list + 1);
0284adc6
LP
279
280 list[n_list].filename = p;
281 list[n_list].usage = 512UL * (uint64_t) st.st_blocks;
282 list[n_list].seqnum = seqnum;
283 list[n_list].realtime = realtime;
284 list[n_list].seqnum_id = seqnum_id;
285 list[n_list].have_seqnum = have_seqnum;
286
287 sum += list[n_list].usage;
288
289 n_list ++;
290 }
291
7ff7394d 292 qsort_safe(list, n_list, sizeof(struct vacuum_info), vacuum_compare);
0284adc6 293
fb0951b0 294 for (i = 0; i < n_list; i++) {
fb0951b0 295 if ((max_retention_usec <= 0 || list[i].realtime >= retention_limit) &&
348ced90 296 (max_use <= 0 || sum <= max_use))
0284adc6
LP
297 break;
298
299 if (unlinkat(dirfd(d), list[i].filename, 0) >= 0) {
dbd2a83f 300 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 301 freed += list[i].usage;
4fa25d62 302
6c142648 303 if (list[i].usage < sum)
4fa25d62
LP
304 sum -= list[i].usage;
305 else
306 sum = 0;
307
0284adc6 308 } else if (errno != ENOENT)
dbd2a83f 309 log_warning("Failed to delete archived journal %s/%s: %m", directory, list[i].filename);
0284adc6
LP
310 }
311
fb0951b0
LP
312 if (oldest_usec && i < n_list && (*oldest_usec == 0 || list[i].realtime < *oldest_usec))
313 *oldest_usec = list[i].realtime;
314
0284adc6
LP
315finish:
316 for (i = 0; i < n_list; i++)
317 free(list[i].filename);
0284adc6
LP
318 free(list);
319
dbd2a83f 320 log_full(verbose ? LOG_INFO : LOG_DEBUG, "Vacuuming done, freed %s of archived journals on disk.", format_bytes(sbytes, sizeof(sbytes), freed));
289f910e 321
0284adc6
LP
322 return r;
323}