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