]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/journal/journal-vacuum.c
util-lib: split out allocation calls into alloc-util.[ch]
[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 <fcntl.h>
23 #include <sys/stat.h>
24 #include <unistd.h>
25
26 #include "sd-id128.h"
27
28 #include "alloc-util.h"
29 #include "dirent-util.h"
30 #include "fd-util.h"
31 #include "journal-def.h"
32 #include "journal-file.h"
33 #include "journal-vacuum.h"
34 #include "parse-util.h"
35 #include "string-util.h"
36 #include "util.h"
37 #include "xattr-util.h"
38
39 struct vacuum_info {
40 uint64_t usage;
41 char *filename;
42
43 uint64_t realtime;
44
45 sd_id128_t seqnum_id;
46 uint64_t seqnum;
47 bool have_seqnum;
48 };
49
50 static int vacuum_compare(const void *_a, const void *_b) {
51 const struct vacuum_info *a, *b;
52
53 a = _a;
54 b = _b;
55
56 if (a->have_seqnum && b->have_seqnum &&
57 sd_id128_equal(a->seqnum_id, b->seqnum_id)) {
58 if (a->seqnum < b->seqnum)
59 return -1;
60 else if (a->seqnum > b->seqnum)
61 return 1;
62 else
63 return 0;
64 }
65
66 if (a->realtime < b->realtime)
67 return -1;
68 else if (a->realtime > b->realtime)
69 return 1;
70 else if (a->have_seqnum && b->have_seqnum)
71 return memcmp(&a->seqnum_id, &b->seqnum_id, 16);
72 else
73 return strcmp(a->filename, b->filename);
74 }
75
76 static void patch_realtime(
77 int fd,
78 const char *fn,
79 const struct stat *st,
80 unsigned long long *realtime) {
81
82 usec_t x, crtime = 0;
83
84 /* The timestamp was determined by the file name, but let's
85 * see if the file might actually be older than the file name
86 * suggested... */
87
88 assert(fd >= 0);
89 assert(fn);
90 assert(st);
91 assert(realtime);
92
93 x = timespec_load(&st->st_ctim);
94 if (x > 0 && x != USEC_INFINITY && x < *realtime)
95 *realtime = x;
96
97 x = timespec_load(&st->st_atim);
98 if (x > 0 && x != USEC_INFINITY && x < *realtime)
99 *realtime = x;
100
101 x = timespec_load(&st->st_mtim);
102 if (x > 0 && x != USEC_INFINITY && x < *realtime)
103 *realtime = x;
104
105 /* Let's read the original creation time, if possible. Ideally
106 * we'd just query the creation time the FS might provide, but
107 * unfortunately there's currently no sane API to query
108 * it. Hence let's implement this manually... */
109
110 if (fd_getcrtime_at(fd, fn, &crtime, 0) >= 0) {
111 if (crtime < *realtime)
112 *realtime = crtime;
113 }
114 }
115
116 static int journal_file_empty(int dir_fd, const char *name) {
117 _cleanup_close_ int fd;
118 struct stat st;
119 le64_t n_entries;
120 ssize_t n;
121
122 fd = openat(dir_fd, name, O_RDONLY|O_CLOEXEC|O_NOFOLLOW|O_NONBLOCK|O_NOATIME);
123 if (fd < 0) {
124 /* Maybe failed due to O_NOATIME and lack of privileges? */
125 fd = openat(dir_fd, name, O_RDONLY|O_CLOEXEC|O_NOFOLLOW|O_NONBLOCK);
126 if (fd < 0)
127 return -errno;
128 }
129
130 if (fstat(fd, &st) < 0)
131 return -errno;
132
133 /* If an offline file doesn't even have a header we consider it empty */
134 if (st.st_size < (off_t) sizeof(Header))
135 return 1;
136
137 /* If the number of entries is empty, we consider it empty, too */
138 n = pread(fd, &n_entries, sizeof(n_entries), offsetof(Header, n_entries));
139 if (n < 0)
140 return -errno;
141 if (n != sizeof(n_entries))
142 return -EIO;
143
144 return le64toh(n_entries) <= 0;
145 }
146
147 int journal_directory_vacuum(
148 const char *directory,
149 uint64_t max_use,
150 uint64_t n_max_files,
151 usec_t max_retention_usec,
152 usec_t *oldest_usec,
153 bool verbose) {
154
155 _cleanup_closedir_ DIR *d = NULL;
156 struct vacuum_info *list = NULL;
157 unsigned n_list = 0, i, n_active_files = 0;
158 size_t n_allocated = 0;
159 uint64_t sum = 0, freed = 0;
160 usec_t retention_limit = 0;
161 char sbytes[FORMAT_BYTES_MAX];
162 struct dirent *de;
163 int r;
164
165 assert(directory);
166
167 if (max_use <= 0 && max_retention_usec <= 0 && n_max_files <= 0)
168 return 0;
169
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
178 d = opendir(directory);
179 if (!d)
180 return -errno;
181
182 FOREACH_DIRENT_ALL(de, d, r = -errno; goto finish) {
183
184 unsigned long long seqnum = 0, realtime;
185 _cleanup_free_ char *p = NULL;
186 sd_id128_t seqnum_id;
187 bool have_seqnum;
188 uint64_t size;
189 struct stat st;
190 size_t q;
191
192 if (fstatat(dirfd(d), de->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0) {
193 log_debug_errno(errno, "Failed to stat file %s while vacuuming, ignoring: %m", de->d_name);
194 continue;
195 }
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. Active files are
205 * left around */
206
207 if (q < 1 + 32 + 1 + 16 + 1 + 16 + 8) {
208 n_active_files++;
209 continue;
210 }
211
212 if (de->d_name[q-8-16-1] != '-' ||
213 de->d_name[q-8-16-1-16-1] != '-' ||
214 de->d_name[q-8-16-1-16-1-32-1] != '@') {
215 n_active_files++;
216 continue;
217 }
218
219 p = strdup(de->d_name);
220 if (!p) {
221 r = -ENOMEM;
222 goto finish;
223 }
224
225 de->d_name[q-8-16-1-16-1] = 0;
226 if (sd_id128_from_string(de->d_name + q-8-16-1-16-1-32, &seqnum_id) < 0) {
227 n_active_files++;
228 continue;
229 }
230
231 if (sscanf(de->d_name + q-8-16-1-16, "%16llx-%16llx.journal", &seqnum, &realtime) != 2) {
232 n_active_files++;
233 continue;
234 }
235
236 have_seqnum = true;
237
238 } else if (endswith(de->d_name, ".journal~")) {
239 unsigned long long tmp;
240
241 /* Vacuum corrupted files */
242
243 if (q < 1 + 16 + 1 + 16 + 8 + 1) {
244 n_active_files ++;
245 continue;
246 }
247
248 if (de->d_name[q-1-8-16-1] != '-' ||
249 de->d_name[q-1-8-16-1-16-1] != '@') {
250 n_active_files ++;
251 continue;
252 }
253
254 p = strdup(de->d_name);
255 if (!p) {
256 r = -ENOMEM;
257 goto finish;
258 }
259
260 if (sscanf(de->d_name + q-1-8-16-1-16, "%16llx-%16llx.journal~", &realtime, &tmp) != 2) {
261 n_active_files ++;
262 continue;
263 }
264
265 have_seqnum = false;
266 } else {
267 /* We do not vacuum unknown files! */
268 log_debug("Not vacuuming unknown file %s.", de->d_name);
269 continue;
270 }
271
272 size = 512UL * (uint64_t) st.st_blocks;
273
274 r = journal_file_empty(dirfd(d), p);
275 if (r < 0) {
276 log_debug_errno(r, "Failed check if %s is empty, ignoring: %m", p);
277 continue;
278 }
279 if (r > 0) {
280 /* Always vacuum empty non-online files. */
281
282 if (unlinkat(dirfd(d), p, 0) >= 0) {
283
284 log_full(verbose ? LOG_INFO : LOG_DEBUG,
285 "Deleted empty archived journal %s/%s (%s).", directory, p, format_bytes(sbytes, sizeof(sbytes), size));
286
287 freed += size;
288 } else if (errno != ENOENT)
289 log_warning_errno(errno, "Failed to delete empty archived journal %s/%s: %m", directory, p);
290
291 continue;
292 }
293
294 patch_realtime(dirfd(d), p, &st, &realtime);
295
296 if (!GREEDY_REALLOC(list, n_allocated, n_list + 1)) {
297 r = -ENOMEM;
298 goto finish;
299 }
300
301 list[n_list].filename = p;
302 list[n_list].usage = size;
303 list[n_list].seqnum = seqnum;
304 list[n_list].realtime = realtime;
305 list[n_list].seqnum_id = seqnum_id;
306 list[n_list].have_seqnum = have_seqnum;
307 n_list ++;
308
309 p = NULL;
310 sum += size;
311 }
312
313 qsort_safe(list, n_list, sizeof(struct vacuum_info), vacuum_compare);
314
315 for (i = 0; i < n_list; i++) {
316 unsigned left;
317
318 left = n_active_files + n_list - i;
319
320 if ((max_retention_usec <= 0 || list[i].realtime >= retention_limit) &&
321 (max_use <= 0 || sum <= max_use) &&
322 (n_max_files <= 0 || left <= n_max_files))
323 break;
324
325 if (unlinkat(dirfd(d), list[i].filename, 0) >= 0) {
326 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));
327 freed += list[i].usage;
328
329 if (list[i].usage < sum)
330 sum -= list[i].usage;
331 else
332 sum = 0;
333
334 } else if (errno != ENOENT)
335 log_warning_errno(errno, "Failed to delete archived journal %s/%s: %m", directory, list[i].filename);
336 }
337
338 if (oldest_usec && i < n_list && (*oldest_usec == 0 || list[i].realtime < *oldest_usec))
339 *oldest_usec = list[i].realtime;
340
341 r = 0;
342
343 finish:
344 for (i = 0; i < n_list; i++)
345 free(list[i].filename);
346 free(list);
347
348 log_full(verbose ? LOG_INFO : LOG_DEBUG, "Vacuuming done, freed %s of archived journals on disk.", format_bytes(sbytes, sizeof(sbytes), freed));
349
350 return r;
351 }