]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/journal/journal-vacuum.c
journal: port over to fd_getcrtime_at()
[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);
116 if (fd < 0)
117 return -errno;
118
119 if (fstat(fd, &st) < 0)
120 return -errno;
121
122 /* If an offline file doesn't even have a header we consider it empty */
123 if (st.st_size < (off_t) sizeof(Header))
124 return 1;
125
126 /* If the number of entries is empty, we consider it empty, too */
127 n = pread(fd, &n_entries, sizeof(n_entries), offsetof(Header, n_entries));
128 if (n < 0)
129 return -errno;
130 if (n != sizeof(n_entries))
131 return -EIO;
132
133 return le64toh(n_entries) <= 0;
134 }
135
136 int journal_directory_vacuum(
137 const char *directory,
138 uint64_t max_use,
139 usec_t max_retention_usec,
140 usec_t *oldest_usec,
141 bool verbose) {
142
143 _cleanup_closedir_ DIR *d = NULL;
144 int r = 0;
145 struct vacuum_info *list = NULL;
146 unsigned n_list = 0, i;
147 size_t n_allocated = 0;
148 uint64_t sum = 0, freed = 0;
149 usec_t retention_limit = 0;
150 char sbytes[FORMAT_BYTES_MAX];
151
152 assert(directory);
153
154 if (max_use <= 0 && max_retention_usec <= 0)
155 return 0;
156
157 if (max_retention_usec > 0) {
158 retention_limit = now(CLOCK_REALTIME);
159 if (retention_limit > max_retention_usec)
160 retention_limit -= max_retention_usec;
161 else
162 max_retention_usec = retention_limit = 0;
163 }
164
165 d = opendir(directory);
166 if (!d)
167 return -errno;
168
169 for (;;) {
170 struct dirent *de;
171 size_t q;
172 struct stat st;
173 char *p;
174 unsigned long long seqnum = 0, realtime;
175 sd_id128_t seqnum_id;
176 bool have_seqnum;
177
178 errno = 0;
179 de = readdir(d);
180 if (!de && errno != 0) {
181 r = -errno;
182 goto finish;
183 }
184
185 if (!de)
186 break;
187
188 if (fstatat(dirfd(d), de->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0)
189 continue;
190
191 if (!S_ISREG(st.st_mode))
192 continue;
193
194 q = strlen(de->d_name);
195
196 if (endswith(de->d_name, ".journal")) {
197
198 /* Vacuum archived files */
199
200 if (q < 1 + 32 + 1 + 16 + 1 + 16 + 8)
201 continue;
202
203 if (de->d_name[q-8-16-1] != '-' ||
204 de->d_name[q-8-16-1-16-1] != '-' ||
205 de->d_name[q-8-16-1-16-1-32-1] != '@')
206 continue;
207
208 p = strdup(de->d_name);
209 if (!p) {
210 r = -ENOMEM;
211 goto finish;
212 }
213
214 de->d_name[q-8-16-1-16-1] = 0;
215 if (sd_id128_from_string(de->d_name + q-8-16-1-16-1-32, &seqnum_id) < 0) {
216 free(p);
217 continue;
218 }
219
220 if (sscanf(de->d_name + q-8-16-1-16, "%16llx-%16llx.journal", &seqnum, &realtime) != 2) {
221 free(p);
222 continue;
223 }
224
225 have_seqnum = true;
226
227 } else if (endswith(de->d_name, ".journal~")) {
228 unsigned long long tmp;
229
230 /* Vacuum corrupted files */
231
232 if (q < 1 + 16 + 1 + 16 + 8 + 1)
233 continue;
234
235 if (de->d_name[q-1-8-16-1] != '-' ||
236 de->d_name[q-1-8-16-1-16-1] != '@')
237 continue;
238
239 p = strdup(de->d_name);
240 if (!p) {
241 r = -ENOMEM;
242 goto finish;
243 }
244
245 if (sscanf(de->d_name + q-1-8-16-1-16, "%16llx-%16llx.journal~", &realtime, &tmp) != 2) {
246 free(p);
247 continue;
248 }
249
250 have_seqnum = false;
251 } else
252 /* We do not vacuum active files or unknown files! */
253 continue;
254
255 if (journal_file_empty(dirfd(d), p)) {
256 /* Always vacuum empty non-online files. */
257
258 uint64_t size = 512UL * (uint64_t) st.st_blocks;
259
260 if (unlinkat(dirfd(d), p, 0) >= 0) {
261 log_full(verbose ? LOG_INFO : LOG_DEBUG, "Deleted empty archived journal %s/%s (%s).", directory, p, format_bytes(sbytes, sizeof(sbytes), size));
262 freed += size;
263 } else if (errno != ENOENT)
264 log_warning_errno(errno, "Failed to delete empty archived journal %s/%s: %m", directory, p);
265
266 free(p);
267 continue;
268 }
269
270 patch_realtime(directory, p, &st, &realtime);
271
272 if (!GREEDY_REALLOC(list, n_allocated, n_list + 1)) {
273 free(p);
274 r = -ENOMEM;
275 goto finish;
276 }
277
278 list[n_list].filename = p;
279 list[n_list].usage = 512UL * (uint64_t) st.st_blocks;
280 list[n_list].seqnum = seqnum;
281 list[n_list].realtime = realtime;
282 list[n_list].seqnum_id = seqnum_id;
283 list[n_list].have_seqnum = have_seqnum;
284
285 sum += list[n_list].usage;
286
287 n_list ++;
288 }
289
290 qsort_safe(list, n_list, sizeof(struct vacuum_info), vacuum_compare);
291
292 for (i = 0; i < n_list; i++) {
293 if ((max_retention_usec <= 0 || list[i].realtime >= retention_limit) &&
294 (max_use <= 0 || sum <= max_use))
295 break;
296
297 if (unlinkat(dirfd(d), list[i].filename, 0) >= 0) {
298 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));
299 freed += list[i].usage;
300
301 if (list[i].usage < sum)
302 sum -= list[i].usage;
303 else
304 sum = 0;
305
306 } else if (errno != ENOENT)
307 log_warning_errno(errno, "Failed to delete archived journal %s/%s: %m", directory, list[i].filename);
308 }
309
310 if (oldest_usec && i < n_list && (*oldest_usec == 0 || list[i].realtime < *oldest_usec))
311 *oldest_usec = list[i].realtime;
312
313 finish:
314 for (i = 0; i < n_list; i++)
315 free(list[i].filename);
316 free(list);
317
318 log_full(verbose ? LOG_INFO : LOG_DEBUG, "Vacuuming done, freed %s of archived journals on disk.", format_bytes(sbytes, sizeof(sbytes), freed));
319
320 return r;
321 }