]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/journal/journal-vacuum.c
remove unused includes
[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 sd_id128_t seqnum_id;
38 uint64_t seqnum;
39
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 const char *dir,
71 const char *fn,
72 const struct stat *st,
73 unsigned long long *realtime) {
74
75 _cleanup_free_ const char *path = NULL;
76 usec_t x, crtime;
77
78 /* The timestamp was determined by the file name, but let's
79 * see if the file might actually be older than the file name
80 * suggested... */
81
82 assert(dir);
83 assert(fn);
84 assert(st);
85 assert(realtime);
86
87 x = timespec_load(&st->st_ctim);
88 if (x > 0 && x != USEC_INFINITY && x < *realtime)
89 *realtime = x;
90
91 x = timespec_load(&st->st_atim);
92 if (x > 0 && x != USEC_INFINITY && x < *realtime)
93 *realtime = x;
94
95 x = timespec_load(&st->st_mtim);
96 if (x > 0 && x != USEC_INFINITY && x < *realtime)
97 *realtime = x;
98
99 /* Let's read the original creation time, if possible. Ideally
100 * we'd just query the creation time the FS might provide, but
101 * unfortunately there's currently no sane API to query
102 * it. Hence let's implement this manually... */
103
104 /* Unfortunately there is is not fgetxattrat(), so we need to
105 * go via path here. :-( */
106
107 path = strjoin(dir, "/", fn, NULL);
108 if (!path)
109 return;
110
111 if (path_getcrtime(path, &crtime) >= 0) {
112 if (crtime < *realtime)
113 *realtime = crtime;
114 }
115 }
116
117 static int journal_file_empty(int dir_fd, const char *name) {
118 _cleanup_close_ int fd;
119 struct stat st;
120 le64_t n_entries;
121 ssize_t n;
122
123 fd = openat(dir_fd, name, O_RDONLY|O_CLOEXEC|O_NOFOLLOW|O_NONBLOCK);
124 if (fd < 0)
125 return -errno;
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 usec_t max_retention_usec,
148 usec_t *oldest_usec,
149 bool verbose) {
150
151 _cleanup_closedir_ DIR *d = NULL;
152 int r = 0;
153 struct vacuum_info *list = NULL;
154 unsigned n_list = 0, i;
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
160 assert(directory);
161
162 if (max_use <= 0 && max_retention_usec <= 0)
163 return 0;
164
165 if (max_retention_usec > 0) {
166 retention_limit = now(CLOCK_REALTIME);
167 if (retention_limit > max_retention_usec)
168 retention_limit -= max_retention_usec;
169 else
170 max_retention_usec = retention_limit = 0;
171 }
172
173 d = opendir(directory);
174 if (!d)
175 return -errno;
176
177 for (;;) {
178 struct dirent *de;
179 size_t q;
180 struct stat st;
181 char *p;
182 unsigned long long seqnum = 0, realtime;
183 sd_id128_t seqnum_id;
184 bool have_seqnum;
185
186 errno = 0;
187 de = readdir(d);
188 if (!de && errno != 0) {
189 r = -errno;
190 goto finish;
191 }
192
193 if (!de)
194 break;
195
196 if (fstatat(dirfd(d), de->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0)
197 continue;
198
199 if (!S_ISREG(st.st_mode))
200 continue;
201
202 q = strlen(de->d_name);
203
204 if (endswith(de->d_name, ".journal")) {
205
206 /* Vacuum archived files */
207
208 if (q < 1 + 32 + 1 + 16 + 1 + 16 + 8)
209 continue;
210
211 if (de->d_name[q-8-16-1] != '-' ||
212 de->d_name[q-8-16-1-16-1] != '-' ||
213 de->d_name[q-8-16-1-16-1-32-1] != '@')
214 continue;
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 free(p);
225 continue;
226 }
227
228 if (sscanf(de->d_name + q-8-16-1-16, "%16llx-%16llx.journal", &seqnum, &realtime) != 2) {
229 free(p);
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 continue;
242
243 if (de->d_name[q-1-8-16-1] != '-' ||
244 de->d_name[q-1-8-16-1-16-1] != '@')
245 continue;
246
247 p = strdup(de->d_name);
248 if (!p) {
249 r = -ENOMEM;
250 goto finish;
251 }
252
253 if (sscanf(de->d_name + q-1-8-16-1-16, "%16llx-%16llx.journal~", &realtime, &tmp) != 2) {
254 free(p);
255 continue;
256 }
257
258 have_seqnum = false;
259 } else
260 /* We do not vacuum active files or unknown files! */
261 continue;
262
263 if (journal_file_empty(dirfd(d), p)) {
264 /* Always vacuum empty non-online files. */
265
266 uint64_t size = 512UL * (uint64_t) st.st_blocks;
267
268 if (unlinkat(dirfd(d), p, 0) >= 0) {
269 log_full(verbose ? LOG_INFO : LOG_DEBUG, "Deleted empty archived journal %s/%s (%s).", directory, p, format_bytes(sbytes, sizeof(sbytes), size));
270 freed += size;
271 } else if (errno != ENOENT)
272 log_warning_errno(errno, "Failed to delete empty archived journal %s/%s: %m", directory, p);
273
274 free(p);
275 continue;
276 }
277
278 patch_realtime(directory, p, &st, &realtime);
279
280 if (!GREEDY_REALLOC(list, n_allocated, n_list + 1)) {
281 free(p);
282 r = -ENOMEM;
283 goto finish;
284 }
285
286 list[n_list].filename = p;
287 list[n_list].usage = 512UL * (uint64_t) st.st_blocks;
288 list[n_list].seqnum = seqnum;
289 list[n_list].realtime = realtime;
290 list[n_list].seqnum_id = seqnum_id;
291 list[n_list].have_seqnum = have_seqnum;
292
293 sum += list[n_list].usage;
294
295 n_list ++;
296 }
297
298 qsort_safe(list, n_list, sizeof(struct vacuum_info), vacuum_compare);
299
300 for (i = 0; i < n_list; i++) {
301 if ((max_retention_usec <= 0 || list[i].realtime >= retention_limit) &&
302 (max_use <= 0 || sum <= max_use))
303 break;
304
305 if (unlinkat(dirfd(d), list[i].filename, 0) >= 0) {
306 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));
307 freed += list[i].usage;
308
309 if (list[i].usage < sum)
310 sum -= list[i].usage;
311 else
312 sum = 0;
313
314 } else if (errno != ENOENT)
315 log_warning_errno(errno, "Failed to delete archived journal %s/%s: %m", directory, list[i].filename);
316 }
317
318 if (oldest_usec && i < n_list && (*oldest_usec == 0 || list[i].realtime < *oldest_usec))
319 *oldest_usec = list[i].realtime;
320
321 finish:
322 for (i = 0; i < n_list; i++)
323 free(list[i].filename);
324 free(list);
325
326 log_full(verbose ? LOG_INFO : LOG_DEBUG, "Vacuuming done, freed %s of archived journals on disk.", format_bytes(sbytes, sizeof(sbytes), freed));
327
328 return r;
329 }