]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/journal/journal-vacuum.c
journal: don't affect atime of journal files when vacuuming
[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 usec_t max_retention_usec,
144 usec_t *oldest_usec,
145 bool verbose) {
146
147 _cleanup_closedir_ DIR *d = NULL;
148 int r = 0;
149 struct vacuum_info *list = NULL;
150 unsigned n_list = 0, i;
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
156 assert(directory);
157
158 if (max_use <= 0 && max_retention_usec <= 0)
159 return 0;
160
161 if (max_retention_usec > 0) {
162 retention_limit = now(CLOCK_REALTIME);
163 if (retention_limit > max_retention_usec)
164 retention_limit -= max_retention_usec;
165 else
166 max_retention_usec = retention_limit = 0;
167 }
168
169 d = opendir(directory);
170 if (!d)
171 return -errno;
172
173 for (;;) {
174 struct dirent *de;
175 size_t q;
176 struct stat st;
177 char *p;
178 unsigned long long seqnum = 0, realtime;
179 sd_id128_t seqnum_id;
180 bool have_seqnum;
181
182 errno = 0;
183 de = readdir(d);
184 if (!de && errno != 0) {
185 r = -errno;
186 goto finish;
187 }
188
189 if (!de)
190 break;
191
192 if (fstatat(dirfd(d), de->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0)
193 continue;
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 */
203
204 if (q < 1 + 32 + 1 + 16 + 1 + 16 + 8)
205 continue;
206
207 if (de->d_name[q-8-16-1] != '-' ||
208 de->d_name[q-8-16-1-16-1] != '-' ||
209 de->d_name[q-8-16-1-16-1-32-1] != '@')
210 continue;
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 continue;
222 }
223
224 if (sscanf(de->d_name + q-8-16-1-16, "%16llx-%16llx.journal", &seqnum, &realtime) != 2) {
225 free(p);
226 continue;
227 }
228
229 have_seqnum = true;
230
231 } else if (endswith(de->d_name, ".journal~")) {
232 unsigned long long tmp;
233
234 /* Vacuum corrupted files */
235
236 if (q < 1 + 16 + 1 + 16 + 8 + 1)
237 continue;
238
239 if (de->d_name[q-1-8-16-1] != '-' ||
240 de->d_name[q-1-8-16-1-16-1] != '@')
241 continue;
242
243 p = strdup(de->d_name);
244 if (!p) {
245 r = -ENOMEM;
246 goto finish;
247 }
248
249 if (sscanf(de->d_name + q-1-8-16-1-16, "%16llx-%16llx.journal~", &realtime, &tmp) != 2) {
250 free(p);
251 continue;
252 }
253
254 have_seqnum = false;
255 } else
256 /* We do not vacuum active files or unknown files! */
257 continue;
258
259 if (journal_file_empty(dirfd(d), p)) {
260 /* Always vacuum empty non-online files. */
261
262 uint64_t size = 512UL * (uint64_t) st.st_blocks;
263
264 if (unlinkat(dirfd(d), p, 0) >= 0) {
265 log_full(verbose ? LOG_INFO : LOG_DEBUG, "Deleted empty archived journal %s/%s (%s).", directory, p, format_bytes(sbytes, sizeof(sbytes), size));
266 freed += size;
267 } else if (errno != ENOENT)
268 log_warning_errno(errno, "Failed to delete empty archived journal %s/%s: %m", directory, p);
269
270 free(p);
271 continue;
272 }
273
274 patch_realtime(directory, p, &st, &realtime);
275
276 if (!GREEDY_REALLOC(list, n_allocated, n_list + 1)) {
277 free(p);
278 r = -ENOMEM;
279 goto finish;
280 }
281
282 list[n_list].filename = p;
283 list[n_list].usage = 512UL * (uint64_t) st.st_blocks;
284 list[n_list].seqnum = seqnum;
285 list[n_list].realtime = realtime;
286 list[n_list].seqnum_id = seqnum_id;
287 list[n_list].have_seqnum = have_seqnum;
288
289 sum += list[n_list].usage;
290
291 n_list ++;
292 }
293
294 qsort_safe(list, n_list, sizeof(struct vacuum_info), vacuum_compare);
295
296 for (i = 0; i < n_list; i++) {
297 if ((max_retention_usec <= 0 || list[i].realtime >= retention_limit) &&
298 (max_use <= 0 || sum <= max_use))
299 break;
300
301 if (unlinkat(dirfd(d), list[i].filename, 0) >= 0) {
302 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));
303 freed += list[i].usage;
304
305 if (list[i].usage < sum)
306 sum -= list[i].usage;
307 else
308 sum = 0;
309
310 } else if (errno != ENOENT)
311 log_warning_errno(errno, "Failed to delete archived journal %s/%s: %m", directory, list[i].filename);
312 }
313
314 if (oldest_usec && i < n_list && (*oldest_usec == 0 || list[i].realtime < *oldest_usec))
315 *oldest_usec = list[i].realtime;
316
317 finish:
318 for (i = 0; i < n_list; i++)
319 free(list[i].filename);
320 free(list);
321
322 log_full(verbose ? LOG_INFO : LOG_DEBUG, "Vacuuming done, freed %s of archived journals on disk.", format_bytes(sbytes, sizeof(sbytes), freed));
323
324 return r;
325 }