]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/journal/journal-vacuum.c
journald: fix space limits reporting
[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
131int journal_directory_vacuum(
132 const char *directory,
133 uint64_t max_use,
134 uint64_t min_free,
135 usec_t max_retention_usec,
136 usec_t *oldest_usec) {
137
30cb029b 138 _cleanup_closedir_ DIR *d = NULL;
0284adc6
LP
139 int r = 0;
140 struct vacuum_info *list = NULL;
30cb029b
ZJS
141 unsigned n_list = 0, i;
142 size_t n_allocated = 0;
0284adc6 143 uint64_t sum = 0;
fb0951b0 144 usec_t retention_limit = 0;
0284adc6
LP
145
146 assert(directory);
147
fb0951b0 148 if (max_use <= 0 && min_free <= 0 && max_retention_usec <= 0)
0284adc6
LP
149 return 0;
150
fb0951b0
LP
151 if (max_retention_usec > 0) {
152 retention_limit = now(CLOCK_REALTIME);
153 if (retention_limit > max_retention_usec)
154 retention_limit -= max_retention_usec;
155 else
156 max_retention_usec = retention_limit = 0;
157 }
158
0284adc6
LP
159 d = opendir(directory);
160 if (!d)
161 return -errno;
162
163 for (;;) {
164 int k;
7d5e9c0f
LP
165 struct dirent *de;
166 union dirent_storage buf;
0284adc6
LP
167 size_t q;
168 struct stat st;
169 char *p;
170 unsigned long long seqnum = 0, realtime;
171 sd_id128_t seqnum_id;
172 bool have_seqnum;
173
7d5e9c0f 174 k = readdir_r(d, &buf.de, &de);
0284adc6
LP
175 if (k != 0) {
176 r = -k;
177 goto finish;
178 }
179
180 if (!de)
181 break;
182
183 if (fstatat(dirfd(d), de->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0)
184 continue;
185
186 if (!S_ISREG(st.st_mode))
187 continue;
188
189 q = strlen(de->d_name);
190
191 if (endswith(de->d_name, ".journal")) {
192
193 /* Vacuum archived files */
194
195 if (q < 1 + 32 + 1 + 16 + 1 + 16 + 8)
196 continue;
197
198 if (de->d_name[q-8-16-1] != '-' ||
199 de->d_name[q-8-16-1-16-1] != '-' ||
200 de->d_name[q-8-16-1-16-1-32-1] != '@')
201 continue;
202
203 p = strdup(de->d_name);
204 if (!p) {
205 r = -ENOMEM;
206 goto finish;
207 }
208
209 de->d_name[q-8-16-1-16-1] = 0;
210 if (sd_id128_from_string(de->d_name + q-8-16-1-16-1-32, &seqnum_id) < 0) {
211 free(p);
212 continue;
213 }
214
215 if (sscanf(de->d_name + q-8-16-1-16, "%16llx-%16llx.journal", &seqnum, &realtime) != 2) {
216 free(p);
217 continue;
218 }
219
220 have_seqnum = true;
221
222 } else if (endswith(de->d_name, ".journal~")) {
223 unsigned long long tmp;
224
225 /* Vacuum corrupted files */
226
227 if (q < 1 + 16 + 1 + 16 + 8 + 1)
228 continue;
229
230 if (de->d_name[q-1-8-16-1] != '-' ||
231 de->d_name[q-1-8-16-1-16-1] != '@')
232 continue;
233
234 p = strdup(de->d_name);
235 if (!p) {
236 r = -ENOMEM;
237 goto finish;
238 }
239
240 if (sscanf(de->d_name + q-1-8-16-1-16, "%16llx-%16llx.journal~", &realtime, &tmp) != 2) {
241 free(p);
242 continue;
243 }
244
245 have_seqnum = false;
246 } else
4fa25d62 247 /* We do not vacuum active files or unknown files! */
0284adc6
LP
248 continue;
249
fb0951b0
LP
250 patch_realtime(directory, de->d_name, &st, &realtime);
251
30cb029b 252 GREEDY_REALLOC(list, n_allocated, n_list + 1);
0284adc6
LP
253
254 list[n_list].filename = p;
255 list[n_list].usage = 512UL * (uint64_t) st.st_blocks;
256 list[n_list].seqnum = seqnum;
257 list[n_list].realtime = realtime;
258 list[n_list].seqnum_id = seqnum_id;
259 list[n_list].have_seqnum = have_seqnum;
260
261 sum += list[n_list].usage;
262
263 n_list ++;
264 }
265
266 if (n_list > 0)
267 qsort(list, n_list, sizeof(struct vacuum_info), vacuum_compare);
268
fb0951b0 269 for (i = 0; i < n_list; i++) {
0284adc6
LP
270 struct statvfs ss;
271
272 if (fstatvfs(dirfd(d), &ss) < 0) {
273 r = -errno;
274 goto finish;
275 }
276
fb0951b0
LP
277 if ((max_retention_usec <= 0 || list[i].realtime >= retention_limit) &&
278 (max_use <= 0 || sum <= max_use) &&
279 (min_free <= 0 || (uint64_t) ss.f_bavail * (uint64_t) ss.f_bsize >= min_free))
0284adc6
LP
280 break;
281
282 if (unlinkat(dirfd(d), list[i].filename, 0) >= 0) {
2b43f939 283 log_debug("Deleted archived journal %s/%s.", directory, list[i].filename);
4fa25d62 284
6c142648 285 if (list[i].usage < sum)
4fa25d62
LP
286 sum -= list[i].usage;
287 else
288 sum = 0;
289
0284adc6
LP
290 } else if (errno != ENOENT)
291 log_warning("Failed to delete %s/%s: %m", directory, list[i].filename);
292 }
293
fb0951b0
LP
294 if (oldest_usec && i < n_list && (*oldest_usec == 0 || list[i].realtime < *oldest_usec))
295 *oldest_usec = list[i].realtime;
296
0284adc6
LP
297finish:
298 for (i = 0; i < n_list; i++)
299 free(list[i].filename);
0284adc6
LP
300 free(list);
301
0284adc6
LP
302 return r;
303}