]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/journal/journal-vacuum.c
build-sys: add a makefile target to run all tests through valgrind
[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>
27
fb0951b0
LP
28#ifdef HAVE_XATTR
29#include <attr/xattr.h>
30#endif
31
0284adc6
LP
32#include "journal-def.h"
33#include "journal-file.h"
34#include "journal-vacuum.h"
35#include "sd-id128.h"
36#include "util.h"
37
38struct vacuum_info {
6c142648 39 uint64_t usage;
0284adc6
LP
40 char *filename;
41
42 uint64_t realtime;
43 sd_id128_t seqnum_id;
44 uint64_t seqnum;
45
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
LP
75static void patch_realtime(
76 const char *dir,
77 const char *fn,
78 const struct stat *st,
79 unsigned long long *realtime) {
80
81 usec_t x;
82
83#ifdef HAVE_XATTR
84 uint64_t crtime;
85 _cleanup_free_ const char *path = NULL;
86#endif
87
88 /* The timestamp was determined by the file name, but let's
89 * see if the file might actually be older than the file name
90 * suggested... */
91
92 assert(dir);
93 assert(fn);
94 assert(st);
95 assert(realtime);
96
97 x = timespec_load(&st->st_ctim);
98 if (x > 0 && x != (usec_t) -1 && x < *realtime)
99 *realtime = x;
100
101 x = timespec_load(&st->st_atim);
102 if (x > 0 && x != (usec_t) -1 && x < *realtime)
103 *realtime = x;
104
105 x = timespec_load(&st->st_mtim);
106 if (x > 0 && x != (usec_t) -1 && x < *realtime)
107 *realtime = x;
108
109#ifdef HAVE_XATTR
110 /* Let's read the original creation time, if possible. Ideally
111 * we'd just query the creation time the FS might provide, but
112 * unfortunately there's currently no sane API to query
113 * it. Hence let's implement this manually... */
114
115 /* Unfortunately there is is not fgetxattrat(), so we need to
116 * go via path here. :-( */
117
118 path = strjoin(dir, "/", fn, NULL);
119 if (!path)
120 return;
121
122 if (getxattr(path, "user.crtime_usec", &crtime, sizeof(crtime)) == sizeof(crtime)) {
123 crtime = le64toh(crtime);
124
125 if (crtime > 0 && crtime != (uint64_t) -1 && crtime < *realtime)
126 *realtime = crtime;
127 }
128#endif
129}
130
9d647740 131static int journal_file_empty(int dir_fd, const char *name) {
48979861 132 int r;
9d647740 133 le64_t n_entries;
48979861 134 _cleanup_close_ int fd;
9d647740
ZJS
135
136 fd = openat(dir_fd, name, O_RDONLY|O_CLOEXEC|O_NOFOLLOW|O_NONBLOCK);
137 if (fd < 0)
138 return -errno;
139
140 if (lseek(fd, offsetof(Header, n_entries), SEEK_SET) < 0)
141 return -errno;
142
143 r = read(fd, &n_entries, sizeof(n_entries));
144 if (r != sizeof(n_entries))
145 return r == 0 ? -EINVAL : -errno;
146
147 return le64toh(n_entries) == 0;
148}
149
fb0951b0
LP
150int journal_directory_vacuum(
151 const char *directory,
152 uint64_t max_use,
153 uint64_t min_free,
154 usec_t max_retention_usec,
155 usec_t *oldest_usec) {
156
30cb029b 157 _cleanup_closedir_ DIR *d = NULL;
0284adc6
LP
158 int r = 0;
159 struct vacuum_info *list = NULL;
30cb029b
ZJS
160 unsigned n_list = 0, i;
161 size_t n_allocated = 0;
289f910e 162 uint64_t sum = 0, freed = 0;
fb0951b0 163 usec_t retention_limit = 0;
0284adc6
LP
164
165 assert(directory);
166
fb0951b0 167 if (max_use <= 0 && min_free <= 0 && max_retention_usec <= 0)
0284adc6
LP
168 return 0;
169
fb0951b0
LP
170 if (max_retention_usec > 0) {
171 retention_limit = now(CLOCK_REALTIME);
172 if (retention_limit > max_retention_usec)
173 retention_limit -= max_retention_usec;
174 else
175 max_retention_usec = retention_limit = 0;
176 }
177
0284adc6
LP
178 d = opendir(directory);
179 if (!d)
180 return -errno;
181
182 for (;;) {
183 int k;
7d5e9c0f
LP
184 struct dirent *de;
185 union dirent_storage buf;
0284adc6
LP
186 size_t q;
187 struct stat st;
188 char *p;
189 unsigned long long seqnum = 0, realtime;
190 sd_id128_t seqnum_id;
191 bool have_seqnum;
192
7d5e9c0f 193 k = readdir_r(d, &buf.de, &de);
0284adc6
LP
194 if (k != 0) {
195 r = -k;
196 goto finish;
197 }
198
199 if (!de)
200 break;
201
202 if (fstatat(dirfd(d), de->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0)
203 continue;
204
205 if (!S_ISREG(st.st_mode))
206 continue;
207
208 q = strlen(de->d_name);
209
210 if (endswith(de->d_name, ".journal")) {
211
212 /* Vacuum archived files */
213
214 if (q < 1 + 32 + 1 + 16 + 1 + 16 + 8)
215 continue;
216
217 if (de->d_name[q-8-16-1] != '-' ||
218 de->d_name[q-8-16-1-16-1] != '-' ||
219 de->d_name[q-8-16-1-16-1-32-1] != '@')
220 continue;
221
222 p = strdup(de->d_name);
223 if (!p) {
224 r = -ENOMEM;
225 goto finish;
226 }
227
228 de->d_name[q-8-16-1-16-1] = 0;
229 if (sd_id128_from_string(de->d_name + q-8-16-1-16-1-32, &seqnum_id) < 0) {
230 free(p);
231 continue;
232 }
233
234 if (sscanf(de->d_name + q-8-16-1-16, "%16llx-%16llx.journal", &seqnum, &realtime) != 2) {
235 free(p);
236 continue;
237 }
238
239 have_seqnum = true;
240
241 } else if (endswith(de->d_name, ".journal~")) {
242 unsigned long long tmp;
243
244 /* Vacuum corrupted files */
245
246 if (q < 1 + 16 + 1 + 16 + 8 + 1)
247 continue;
248
249 if (de->d_name[q-1-8-16-1] != '-' ||
250 de->d_name[q-1-8-16-1-16-1] != '@')
251 continue;
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) {
260 free(p);
261 continue;
262 }
263
264 have_seqnum = false;
265 } else
4fa25d62 266 /* We do not vacuum active files or unknown files! */
0284adc6
LP
267 continue;
268
629bfc5a 269 if (journal_file_empty(dirfd(d), p)) {
9d647740
ZJS
270 /* Always vacuum empty non-online files. */
271
289f910e
ZJS
272 uint64_t size = 512UL * (uint64_t) st.st_blocks;
273
274 if (unlinkat(dirfd(d), p, 0) >= 0) {
275 log_info("Deleted empty journal %s/%s (%"PRIu64" bytes).",
276 directory, p, size);
277 freed += size;
278 } else if (errno != ENOENT)
629bfc5a 279 log_warning("Failed to delete %s/%s: %m", directory, p);
289f910e 280
9d647740
ZJS
281 continue;
282 }
283
629bfc5a 284 patch_realtime(directory, p, &st, &realtime);
fb0951b0 285
30cb029b 286 GREEDY_REALLOC(list, n_allocated, n_list + 1);
0284adc6
LP
287
288 list[n_list].filename = p;
289 list[n_list].usage = 512UL * (uint64_t) st.st_blocks;
290 list[n_list].seqnum = seqnum;
291 list[n_list].realtime = realtime;
292 list[n_list].seqnum_id = seqnum_id;
293 list[n_list].have_seqnum = have_seqnum;
294
295 sum += list[n_list].usage;
296
297 n_list ++;
298 }
299
300 if (n_list > 0)
301 qsort(list, n_list, sizeof(struct vacuum_info), vacuum_compare);
302
fb0951b0 303 for (i = 0; i < n_list; i++) {
0284adc6
LP
304 struct statvfs ss;
305
306 if (fstatvfs(dirfd(d), &ss) < 0) {
307 r = -errno;
308 goto finish;
309 }
310
fb0951b0
LP
311 if ((max_retention_usec <= 0 || list[i].realtime >= retention_limit) &&
312 (max_use <= 0 || sum <= max_use) &&
313 (min_free <= 0 || (uint64_t) ss.f_bavail * (uint64_t) ss.f_bsize >= min_free))
0284adc6
LP
314 break;
315
316 if (unlinkat(dirfd(d), list[i].filename, 0) >= 0) {
289f910e
ZJS
317 log_debug("Deleted archived journal %s/%s (%"PRIu64" bytes).",
318 directory, list[i].filename, list[i].usage);
319 freed += list[i].usage;
4fa25d62 320
6c142648 321 if (list[i].usage < sum)
4fa25d62
LP
322 sum -= list[i].usage;
323 else
324 sum = 0;
325
0284adc6
LP
326 } else if (errno != ENOENT)
327 log_warning("Failed to delete %s/%s: %m", directory, list[i].filename);
328 }
329
fb0951b0
LP
330 if (oldest_usec && i < n_list && (*oldest_usec == 0 || list[i].realtime < *oldest_usec))
331 *oldest_usec = list[i].realtime;
332
0284adc6
LP
333finish:
334 for (i = 0; i < n_list; i++)
335 free(list[i].filename);
0284adc6
LP
336 free(list);
337
289f910e
ZJS
338 log_info("Vacuuming done, freed %"PRIu64" bytes", freed);
339
0284adc6
LP
340 return r;
341}