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