]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/journal/journal-vacuum.c
Merge pull request #1485 from jsynacek/machine-long-filename-v5
[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 "journal-def.h"
27 #include "journal-file.h"
28 #include "journal-vacuum.h"
29 #include "sd-id128.h"
30 #include "util.h"
31
32 struct vacuum_info {
33 uint64_t usage;
34 char *filename;
35
36 uint64_t realtime;
37
38 sd_id128_t seqnum_id;
39 uint64_t seqnum;
40 bool have_seqnum;
41 };
42
43 static int vacuum_compare(const void *_a, const void *_b) {
44 const struct vacuum_info *a, *b;
45
46 a = _a;
47 b = _b;
48
49 if (a->have_seqnum && b->have_seqnum &&
50 sd_id128_equal(a->seqnum_id, b->seqnum_id)) {
51 if (a->seqnum < b->seqnum)
52 return -1;
53 else if (a->seqnum > b->seqnum)
54 return 1;
55 else
56 return 0;
57 }
58
59 if (a->realtime < b->realtime)
60 return -1;
61 else if (a->realtime > b->realtime)
62 return 1;
63 else if (a->have_seqnum && b->have_seqnum)
64 return memcmp(&a->seqnum_id, &b->seqnum_id, 16);
65 else
66 return strcmp(a->filename, b->filename);
67 }
68
69 static void patch_realtime(
70 int fd,
71 const char *fn,
72 const struct stat *st,
73 unsigned long long *realtime) {
74
75 usec_t x, crtime = 0;
76
77 /* The timestamp was determined by the file name, but let's
78 * see if the file might actually be older than the file name
79 * suggested... */
80
81 assert(fd >= 0);
82 assert(fn);
83 assert(st);
84 assert(realtime);
85
86 x = timespec_load(&st->st_ctim);
87 if (x > 0 && x != USEC_INFINITY && x < *realtime)
88 *realtime = x;
89
90 x = timespec_load(&st->st_atim);
91 if (x > 0 && x != USEC_INFINITY && x < *realtime)
92 *realtime = x;
93
94 x = timespec_load(&st->st_mtim);
95 if (x > 0 && x != USEC_INFINITY && x < *realtime)
96 *realtime = x;
97
98 /* Let's read the original creation time, if possible. Ideally
99 * we'd just query the creation time the FS might provide, but
100 * unfortunately there's currently no sane API to query
101 * it. Hence let's implement this manually... */
102
103 if (fd_getcrtime_at(fd, fn, &crtime, 0) >= 0) {
104 if (crtime < *realtime)
105 *realtime = crtime;
106 }
107 }
108
109 static int journal_file_empty(int dir_fd, const char *name) {
110 _cleanup_close_ int fd;
111 struct stat st;
112 le64_t n_entries;
113 ssize_t n;
114
115 fd = openat(dir_fd, name, O_RDONLY|O_CLOEXEC|O_NOFOLLOW|O_NONBLOCK|O_NOATIME);
116 if (fd < 0) {
117 /* Maybe failed due to O_NOATIME and lack of privileges? */
118 fd = openat(dir_fd, name, O_RDONLY|O_CLOEXEC|O_NOFOLLOW|O_NONBLOCK);
119 if (fd < 0)
120 return -errno;
121 }
122
123 if (fstat(fd, &st) < 0)
124 return -errno;
125
126 /* If an offline file doesn't even have a header we consider it empty */
127 if (st.st_size < (off_t) sizeof(Header))
128 return 1;
129
130 /* If the number of entries is empty, we consider it empty, too */
131 n = pread(fd, &n_entries, sizeof(n_entries), offsetof(Header, n_entries));
132 if (n < 0)
133 return -errno;
134 if (n != sizeof(n_entries))
135 return -EIO;
136
137 return le64toh(n_entries) <= 0;
138 }
139
140 int journal_directory_vacuum(
141 const char *directory,
142 uint64_t max_use,
143 uint64_t n_max_files,
144 usec_t max_retention_usec,
145 usec_t *oldest_usec,
146 bool verbose) {
147
148 _cleanup_closedir_ DIR *d = NULL;
149 struct vacuum_info *list = NULL;
150 unsigned n_list = 0, i, n_active_files = 0;
151 size_t n_allocated = 0;
152 uint64_t sum = 0, freed = 0;
153 usec_t retention_limit = 0;
154 char sbytes[FORMAT_BYTES_MAX];
155 struct dirent *de;
156 int r;
157
158 assert(directory);
159
160 if (max_use <= 0 && max_retention_usec <= 0 && n_max_files <= 0)
161 return 0;
162
163 if (max_retention_usec > 0) {
164 retention_limit = now(CLOCK_REALTIME);
165 if (retention_limit > max_retention_usec)
166 retention_limit -= max_retention_usec;
167 else
168 max_retention_usec = retention_limit = 0;
169 }
170
171 d = opendir(directory);
172 if (!d)
173 return -errno;
174
175 FOREACH_DIRENT_ALL(de, d, r = -errno; goto finish) {
176
177 unsigned long long seqnum = 0, realtime;
178 _cleanup_free_ char *p = NULL;
179 sd_id128_t seqnum_id;
180 bool have_seqnum;
181 uint64_t size;
182 struct stat st;
183 size_t q;
184
185 if (fstatat(dirfd(d), de->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0) {
186 log_debug_errno(errno, "Failed to stat file %s while vacuuming, ignoring: %m", de->d_name);
187 continue;
188 }
189
190 if (!S_ISREG(st.st_mode))
191 continue;
192
193 q = strlen(de->d_name);
194
195 if (endswith(de->d_name, ".journal")) {
196
197 /* Vacuum archived files. Active files are
198 * left around */
199
200 if (q < 1 + 32 + 1 + 16 + 1 + 16 + 8) {
201 n_active_files++;
202 continue;
203 }
204
205 if (de->d_name[q-8-16-1] != '-' ||
206 de->d_name[q-8-16-1-16-1] != '-' ||
207 de->d_name[q-8-16-1-16-1-32-1] != '@') {
208 n_active_files++;
209 continue;
210 }
211
212 p = strdup(de->d_name);
213 if (!p) {
214 r = -ENOMEM;
215 goto finish;
216 }
217
218 de->d_name[q-8-16-1-16-1] = 0;
219 if (sd_id128_from_string(de->d_name + q-8-16-1-16-1-32, &seqnum_id) < 0) {
220 free(p);
221 n_active_files++;
222 continue;
223 }
224
225 if (sscanf(de->d_name + q-8-16-1-16, "%16llx-%16llx.journal", &seqnum, &realtime) != 2) {
226 free(p);
227 n_active_files++;
228 continue;
229 }
230
231 have_seqnum = true;
232
233 } else if (endswith(de->d_name, ".journal~")) {
234 unsigned long long tmp;
235
236 /* Vacuum corrupted files */
237
238 if (q < 1 + 16 + 1 + 16 + 8 + 1) {
239 n_active_files ++;
240 continue;
241 }
242
243 if (de->d_name[q-1-8-16-1] != '-' ||
244 de->d_name[q-1-8-16-1-16-1] != '@') {
245 n_active_files ++;
246 continue;
247 }
248
249 p = strdup(de->d_name);
250 if (!p) {
251 r = -ENOMEM;
252 goto finish;
253 }
254
255 if (sscanf(de->d_name + q-1-8-16-1-16, "%16llx-%16llx.journal~", &realtime, &tmp) != 2) {
256 free(p);
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 }