]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/journal/journal-vacuum.c
journald: always vacuum empty offline files
[thirdparty/systemd.git] / src / journal / journal-vacuum.c
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
28 #ifdef HAVE_XATTR
29 #include <attr/xattr.h>
30 #endif
31
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
38 struct vacuum_info {
39 uint64_t usage;
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
49 static 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
75 static 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
131 static int journal_file_empty(int dir_fd, const char *name) {
132 int fd, r;
133 le64_t n_entries;
134
135 fd = openat(dir_fd, name, O_RDONLY|O_CLOEXEC|O_NOFOLLOW|O_NONBLOCK);
136 if (fd < 0)
137 return -errno;
138
139 if (lseek(fd, offsetof(Header, n_entries), SEEK_SET) < 0)
140 return -errno;
141
142 r = read(fd, &n_entries, sizeof(n_entries));
143 if (r != sizeof(n_entries))
144 return r == 0 ? -EINVAL : -errno;
145
146 return le64toh(n_entries) == 0;
147 }
148
149 int journal_directory_vacuum(
150 const char *directory,
151 uint64_t max_use,
152 uint64_t min_free,
153 usec_t max_retention_usec,
154 usec_t *oldest_usec) {
155
156 _cleanup_closedir_ DIR *d = NULL;
157 int r = 0;
158 struct vacuum_info *list = NULL;
159 unsigned n_list = 0, i;
160 size_t n_allocated = 0;
161 uint64_t sum = 0;
162 usec_t retention_limit = 0;
163
164 assert(directory);
165
166 if (max_use <= 0 && min_free <= 0 && max_retention_usec <= 0)
167 return 0;
168
169 if (max_retention_usec > 0) {
170 retention_limit = now(CLOCK_REALTIME);
171 if (retention_limit > max_retention_usec)
172 retention_limit -= max_retention_usec;
173 else
174 max_retention_usec = retention_limit = 0;
175 }
176
177 d = opendir(directory);
178 if (!d)
179 return -errno;
180
181 for (;;) {
182 int k;
183 struct dirent *de;
184 union dirent_storage buf;
185 size_t q;
186 struct stat st;
187 char *p;
188 unsigned long long seqnum = 0, realtime;
189 sd_id128_t seqnum_id;
190 bool have_seqnum;
191
192 k = readdir_r(d, &buf.de, &de);
193 if (k != 0) {
194 r = -k;
195 goto finish;
196 }
197
198 if (!de)
199 break;
200
201 if (fstatat(dirfd(d), de->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0)
202 continue;
203
204 if (!S_ISREG(st.st_mode))
205 continue;
206
207 q = strlen(de->d_name);
208
209 if (endswith(de->d_name, ".journal")) {
210
211 /* Vacuum archived files */
212
213 if (q < 1 + 32 + 1 + 16 + 1 + 16 + 8)
214 continue;
215
216 if (de->d_name[q-8-16-1] != '-' ||
217 de->d_name[q-8-16-1-16-1] != '-' ||
218 de->d_name[q-8-16-1-16-1-32-1] != '@')
219 continue;
220
221 p = strdup(de->d_name);
222 if (!p) {
223 r = -ENOMEM;
224 goto finish;
225 }
226
227 de->d_name[q-8-16-1-16-1] = 0;
228 if (sd_id128_from_string(de->d_name + q-8-16-1-16-1-32, &seqnum_id) < 0) {
229 free(p);
230 continue;
231 }
232
233 if (sscanf(de->d_name + q-8-16-1-16, "%16llx-%16llx.journal", &seqnum, &realtime) != 2) {
234 free(p);
235 continue;
236 }
237
238 have_seqnum = true;
239
240 } else if (endswith(de->d_name, ".journal~")) {
241 unsigned long long tmp;
242
243 /* Vacuum corrupted files */
244
245 if (q < 1 + 16 + 1 + 16 + 8 + 1)
246 continue;
247
248 if (de->d_name[q-1-8-16-1] != '-' ||
249 de->d_name[q-1-8-16-1-16-1] != '@')
250 continue;
251
252 p = strdup(de->d_name);
253 if (!p) {
254 r = -ENOMEM;
255 goto finish;
256 }
257
258 if (sscanf(de->d_name + q-1-8-16-1-16, "%16llx-%16llx.journal~", &realtime, &tmp) != 2) {
259 free(p);
260 continue;
261 }
262
263 have_seqnum = false;
264 } else
265 /* We do not vacuum active files or unknown files! */
266 continue;
267
268 if (journal_file_empty(dirfd(d), de->d_name)) {
269
270 /* Always vacuum empty non-online files. */
271
272 if (unlinkat(dirfd(d), de->d_name, 0) >= 0)
273 log_debug("Deleted empty journal %s/%s.", directory, de->d_name);
274 else if (errno != ENOENT)
275 log_warning("Failed to delete %s/%s: %m", directory, de->d_name);
276 continue;
277 }
278
279 patch_realtime(directory, de->d_name, &st, &realtime);
280
281 GREEDY_REALLOC(list, n_allocated, n_list + 1);
282
283 list[n_list].filename = p;
284 list[n_list].usage = 512UL * (uint64_t) st.st_blocks;
285 list[n_list].seqnum = seqnum;
286 list[n_list].realtime = realtime;
287 list[n_list].seqnum_id = seqnum_id;
288 list[n_list].have_seqnum = have_seqnum;
289
290 sum += list[n_list].usage;
291
292 n_list ++;
293 }
294
295 if (n_list > 0)
296 qsort(list, n_list, sizeof(struct vacuum_info), vacuum_compare);
297
298 for (i = 0; i < n_list; i++) {
299 struct statvfs ss;
300
301 if (fstatvfs(dirfd(d), &ss) < 0) {
302 r = -errno;
303 goto finish;
304 }
305
306 if ((max_retention_usec <= 0 || list[i].realtime >= retention_limit) &&
307 (max_use <= 0 || sum <= max_use) &&
308 (min_free <= 0 || (uint64_t) ss.f_bavail * (uint64_t) ss.f_bsize >= min_free))
309 break;
310
311 if (unlinkat(dirfd(d), list[i].filename, 0) >= 0) {
312 log_debug("Deleted archived journal %s/%s.", directory, list[i].filename);
313
314 if (list[i].usage < sum)
315 sum -= list[i].usage;
316 else
317 sum = 0;
318
319 } else if (errno != ENOENT)
320 log_warning("Failed to delete %s/%s: %m", directory, list[i].filename);
321 }
322
323 if (oldest_usec && i < n_list && (*oldest_usec == 0 || list[i].realtime < *oldest_usec))
324 *oldest_usec = list[i].realtime;
325
326 finish:
327 for (i = 0; i < n_list; i++)
328 free(list[i].filename);
329 free(list);
330
331 return r;
332 }