]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/journal/journal-vacuum.c
barrier: convert msecs to usecs in test-code
[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);
92 if (x > 0 && x != (usec_t) -1 && x < *realtime)
93 *realtime = x;
94
95 x = timespec_load(&st->st_atim);
96 if (x > 0 && x != (usec_t) -1 && x < *realtime)
97 *realtime = x;
98
99 x = timespec_load(&st->st_mtim);
100 if (x > 0 && x != (usec_t) -1 && x < *realtime)
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
LP
145 usec_t max_retention_usec,
146 usec_t *oldest_usec) {
147
30cb029b 148 _cleanup_closedir_ DIR *d = NULL;
0284adc6
LP
149 int r = 0;
150 struct vacuum_info *list = NULL;
30cb029b
ZJS
151 unsigned n_list = 0, i;
152 size_t n_allocated = 0;
289f910e 153 uint64_t sum = 0, freed = 0;
fb0951b0 154 usec_t retention_limit = 0;
0284adc6
LP
155
156 assert(directory);
157
348ced90 158 if (max_use <= 0 && max_retention_usec <= 0)
0284adc6
LP
159 return 0;
160
fb0951b0
LP
161 if (max_retention_usec > 0) {
162 retention_limit = now(CLOCK_REALTIME);
163 if (retention_limit > max_retention_usec)
164 retention_limit -= max_retention_usec;
165 else
166 max_retention_usec = retention_limit = 0;
167 }
168
0284adc6
LP
169 d = opendir(directory);
170 if (!d)
171 return -errno;
172
173 for (;;) {
7d5e9c0f 174 struct dirent *de;
0284adc6
LP
175 size_t q;
176 struct stat st;
177 char *p;
178 unsigned long long seqnum = 0, realtime;
179 sd_id128_t seqnum_id;
180 bool have_seqnum;
181
78a5d04d
FW
182 errno = 0;
183 de = readdir(d);
184 if (!de && errno != 0) {
185 r = -errno;
0284adc6
LP
186 goto finish;
187 }
188
189 if (!de)
190 break;
191
192 if (fstatat(dirfd(d), de->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0)
193 continue;
194
195 if (!S_ISREG(st.st_mode))
196 continue;
197
198 q = strlen(de->d_name);
199
200 if (endswith(de->d_name, ".journal")) {
201
202 /* Vacuum archived files */
203
204 if (q < 1 + 32 + 1 + 16 + 1 + 16 + 8)
205 continue;
206
207 if (de->d_name[q-8-16-1] != '-' ||
208 de->d_name[q-8-16-1-16-1] != '-' ||
209 de->d_name[q-8-16-1-16-1-32-1] != '@')
210 continue;
211
212 p = strdup(de->d_name);
213 if (!p) {
214 r = -ENOMEM;
215 goto finish;
216 }
217
218 de->d_name[q-8-16-1-16-1] = 0;
219 if (sd_id128_from_string(de->d_name + q-8-16-1-16-1-32, &seqnum_id) < 0) {
220 free(p);
221 continue;
222 }
223
224 if (sscanf(de->d_name + q-8-16-1-16, "%16llx-%16llx.journal", &seqnum, &realtime) != 2) {
225 free(p);
226 continue;
227 }
228
229 have_seqnum = true;
230
231 } else if (endswith(de->d_name, ".journal~")) {
232 unsigned long long tmp;
233
234 /* Vacuum corrupted files */
235
236 if (q < 1 + 16 + 1 + 16 + 8 + 1)
237 continue;
238
239 if (de->d_name[q-1-8-16-1] != '-' ||
240 de->d_name[q-1-8-16-1-16-1] != '@')
241 continue;
242
243 p = strdup(de->d_name);
244 if (!p) {
245 r = -ENOMEM;
246 goto finish;
247 }
248
249 if (sscanf(de->d_name + q-1-8-16-1-16, "%16llx-%16llx.journal~", &realtime, &tmp) != 2) {
250 free(p);
251 continue;
252 }
253
254 have_seqnum = false;
255 } else
4fa25d62 256 /* We do not vacuum active files or unknown files! */
0284adc6
LP
257 continue;
258
629bfc5a 259 if (journal_file_empty(dirfd(d), p)) {
9d647740
ZJS
260 /* Always vacuum empty non-online files. */
261
289f910e
ZJS
262 uint64_t size = 512UL * (uint64_t) st.st_blocks;
263
264 if (unlinkat(dirfd(d), p, 0) >= 0) {
265 log_info("Deleted empty journal %s/%s (%"PRIu64" bytes).",
266 directory, p, size);
267 freed += size;
268 } else if (errno != ENOENT)
629bfc5a 269 log_warning("Failed to delete %s/%s: %m", directory, p);
289f910e 270
2ee0591d
LP
271 free(p);
272
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) {
289f910e
ZJS
300 log_debug("Deleted archived journal %s/%s (%"PRIu64" bytes).",
301 directory, list[i].filename, list[i].usage);
302 freed += list[i].usage;
4fa25d62 303
6c142648 304 if (list[i].usage < sum)
4fa25d62
LP
305 sum -= list[i].usage;
306 else
307 sum = 0;
308
0284adc6
LP
309 } else if (errno != ENOENT)
310 log_warning("Failed to delete %s/%s: %m", directory, list[i].filename);
311 }
312
fb0951b0
LP
313 if (oldest_usec && i < n_list && (*oldest_usec == 0 || list[i].realtime < *oldest_usec))
314 *oldest_usec = list[i].realtime;
315
0284adc6
LP
316finish:
317 for (i = 0; i < n_list; i++)
318 free(list[i].filename);
0284adc6
LP
319 free(list);
320
3bb621e1 321 log_debug("Vacuuming done, freed %"PRIu64" bytes", freed);
289f910e 322
0284adc6
LP
323 return r;
324}