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