]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/journal/journal-vacuum.c
util: make creation time xattr logic more generic
[thirdparty/systemd.git] / src / journal / journal-vacuum.c
CommitLineData
0284adc6
LP
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 <sys/types.h>
23#include <fcntl.h>
24#include <sys/stat.h>
25#include <sys/statvfs.h>
26#include <unistd.h>
d2edfae0 27#include <sys/xattr.h>
fb0951b0 28
0284adc6
LP
29#include "journal-def.h"
30#include "journal-file.h"
31#include "journal-vacuum.h"
32#include "sd-id128.h"
33#include "util.h"
34
35struct vacuum_info {
6c142648 36 uint64_t usage;
0284adc6
LP
37 char *filename;
38
39 uint64_t realtime;
40 sd_id128_t seqnum_id;
41 uint64_t seqnum;
42
43 bool have_seqnum;
44};
45
46static 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
fb0951b0
LP
72static void patch_realtime(
73 const char *dir,
74 const char *fn,
75 const struct stat *st,
76 unsigned long long *realtime) {
77
fb0951b0 78 _cleanup_free_ const char *path = NULL;
4a4d89b6 79 usec_t x, crtime;
fb0951b0
LP
80
81 /* The timestamp was determined by the file name, but let's
82 * see if the file might actually be older than the file name
83 * suggested... */
84
85 assert(dir);
86 assert(fn);
87 assert(st);
88 assert(realtime);
89
90 x = timespec_load(&st->st_ctim);
3a43da28 91 if (x > 0 && x != USEC_INFINITY && x < *realtime)
fb0951b0
LP
92 *realtime = x;
93
94 x = timespec_load(&st->st_atim);
3a43da28 95 if (x > 0 && x != USEC_INFINITY && x < *realtime)
fb0951b0
LP
96 *realtime = x;
97
98 x = timespec_load(&st->st_mtim);
3a43da28 99 if (x > 0 && x != USEC_INFINITY && x < *realtime)
fb0951b0
LP
100 *realtime = x;
101
fb0951b0
LP
102 /* Let's read the original creation time, if possible. Ideally
103 * we'd just query the creation time the FS might provide, but
104 * unfortunately there's currently no sane API to query
105 * it. Hence let's implement this manually... */
106
107 /* Unfortunately there is is not fgetxattrat(), so we need to
108 * go via path here. :-( */
109
110 path = strjoin(dir, "/", fn, NULL);
111 if (!path)
112 return;
113
4a4d89b6
LP
114 if (path_getcrtime(path, &crtime) >= 0) {
115 if (crtime < *realtime)
fb0951b0
LP
116 *realtime = crtime;
117 }
fb0951b0
LP
118}
119
9d647740 120static int journal_file_empty(int dir_fd, const char *name) {
48979861 121 _cleanup_close_ int fd;
332076b4
LP
122 struct stat st;
123 le64_t n_entries;
124 ssize_t n;
9d647740
ZJS
125
126 fd = openat(dir_fd, name, O_RDONLY|O_CLOEXEC|O_NOFOLLOW|O_NONBLOCK);
127 if (fd < 0)
128 return -errno;
129
332076b4 130 if (fstat(fd, &st) < 0)
9d647740
ZJS
131 return -errno;
132
332076b4
LP
133 /* If an offline file doesn't even have a header we consider it empty */
134 if (st.st_size < (off_t) sizeof(Header))
135 return 1;
136
137 /* If the number of entries is empty, we consider it empty, too */
138 n = pread(fd, &n_entries, sizeof(n_entries), offsetof(Header, n_entries));
139 if (n < 0)
140 return -errno;
141 if (n != sizeof(n_entries))
142 return -EIO;
9d647740 143
332076b4 144 return le64toh(n_entries) <= 0;
9d647740
ZJS
145}
146
fb0951b0
LP
147int journal_directory_vacuum(
148 const char *directory,
149 uint64_t max_use,
fb0951b0 150 usec_t max_retention_usec,
dbd2a83f
LP
151 usec_t *oldest_usec,
152 bool verbose) {
fb0951b0 153
30cb029b 154 _cleanup_closedir_ DIR *d = NULL;
0284adc6
LP
155 int r = 0;
156 struct vacuum_info *list = NULL;
30cb029b
ZJS
157 unsigned n_list = 0, i;
158 size_t n_allocated = 0;
289f910e 159 uint64_t sum = 0, freed = 0;
fb0951b0 160 usec_t retention_limit = 0;
dbd2a83f 161 char sbytes[FORMAT_BYTES_MAX];
0284adc6
LP
162
163 assert(directory);
164
348ced90 165 if (max_use <= 0 && max_retention_usec <= 0)
0284adc6
LP
166 return 0;
167
fb0951b0
LP
168 if (max_retention_usec > 0) {
169 retention_limit = now(CLOCK_REALTIME);
170 if (retention_limit > max_retention_usec)
171 retention_limit -= max_retention_usec;
172 else
173 max_retention_usec = retention_limit = 0;
174 }
175
0284adc6
LP
176 d = opendir(directory);
177 if (!d)
178 return -errno;
179
180 for (;;) {
7d5e9c0f 181 struct dirent *de;
0284adc6
LP
182 size_t q;
183 struct stat st;
184 char *p;
185 unsigned long long seqnum = 0, realtime;
186 sd_id128_t seqnum_id;
187 bool have_seqnum;
188
78a5d04d
FW
189 errno = 0;
190 de = readdir(d);
191 if (!de && errno != 0) {
192 r = -errno;
0284adc6
LP
193 goto finish;
194 }
195
196 if (!de)
197 break;
198
199 if (fstatat(dirfd(d), de->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0)
200 continue;
201
202 if (!S_ISREG(st.st_mode))
203 continue;
204
205 q = strlen(de->d_name);
206
207 if (endswith(de->d_name, ".journal")) {
208
209 /* Vacuum archived files */
210
211 if (q < 1 + 32 + 1 + 16 + 1 + 16 + 8)
212 continue;
213
214 if (de->d_name[q-8-16-1] != '-' ||
215 de->d_name[q-8-16-1-16-1] != '-' ||
216 de->d_name[q-8-16-1-16-1-32-1] != '@')
217 continue;
218
219 p = strdup(de->d_name);
220 if (!p) {
221 r = -ENOMEM;
222 goto finish;
223 }
224
225 de->d_name[q-8-16-1-16-1] = 0;
226 if (sd_id128_from_string(de->d_name + q-8-16-1-16-1-32, &seqnum_id) < 0) {
227 free(p);
228 continue;
229 }
230
231 if (sscanf(de->d_name + q-8-16-1-16, "%16llx-%16llx.journal", &seqnum, &realtime) != 2) {
232 free(p);
233 continue;
234 }
235
236 have_seqnum = true;
237
238 } else if (endswith(de->d_name, ".journal~")) {
239 unsigned long long tmp;
240
241 /* Vacuum corrupted files */
242
243 if (q < 1 + 16 + 1 + 16 + 8 + 1)
244 continue;
245
246 if (de->d_name[q-1-8-16-1] != '-' ||
247 de->d_name[q-1-8-16-1-16-1] != '@')
248 continue;
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 free(p);
258 continue;
259 }
260
261 have_seqnum = false;
262 } else
4fa25d62 263 /* We do not vacuum active files or unknown files! */
0284adc6
LP
264 continue;
265
629bfc5a 266 if (journal_file_empty(dirfd(d), p)) {
9d647740
ZJS
267 /* Always vacuum empty non-online files. */
268
289f910e
ZJS
269 uint64_t size = 512UL * (uint64_t) st.st_blocks;
270
271 if (unlinkat(dirfd(d), p, 0) >= 0) {
dbd2a83f 272 log_full(verbose ? LOG_INFO : LOG_DEBUG, "Deleted empty archived journal %s/%s (%s).", directory, p, format_bytes(sbytes, sizeof(sbytes), size));
289f910e
ZJS
273 freed += size;
274 } else if (errno != ENOENT)
56f64d95 275 log_warning_errno(errno, "Failed to delete empty archived journal %s/%s: %m", directory, p);
289f910e 276
2ee0591d 277 free(p);
9d647740
ZJS
278 continue;
279 }
280
629bfc5a 281 patch_realtime(directory, p, &st, &realtime);
fb0951b0 282
26d8ff04
LP
283 if (!GREEDY_REALLOC(list, n_allocated, n_list + 1)) {
284 free(p);
285 r = -ENOMEM;
286 goto finish;
287 }
0284adc6
LP
288
289 list[n_list].filename = p;
290 list[n_list].usage = 512UL * (uint64_t) st.st_blocks;
291 list[n_list].seqnum = seqnum;
292 list[n_list].realtime = realtime;
293 list[n_list].seqnum_id = seqnum_id;
294 list[n_list].have_seqnum = have_seqnum;
295
296 sum += list[n_list].usage;
297
298 n_list ++;
299 }
300
7ff7394d 301 qsort_safe(list, n_list, sizeof(struct vacuum_info), vacuum_compare);
0284adc6 302
fb0951b0 303 for (i = 0; i < n_list; i++) {
fb0951b0 304 if ((max_retention_usec <= 0 || list[i].realtime >= retention_limit) &&
348ced90 305 (max_use <= 0 || sum <= max_use))
0284adc6
LP
306 break;
307
308 if (unlinkat(dirfd(d), list[i].filename, 0) >= 0) {
dbd2a83f 309 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));
289f910e 310 freed += list[i].usage;
4fa25d62 311
6c142648 312 if (list[i].usage < sum)
4fa25d62
LP
313 sum -= list[i].usage;
314 else
315 sum = 0;
316
0284adc6 317 } else if (errno != ENOENT)
56f64d95 318 log_warning_errno(errno, "Failed to delete archived journal %s/%s: %m", directory, list[i].filename);
0284adc6
LP
319 }
320
fb0951b0
LP
321 if (oldest_usec && i < n_list && (*oldest_usec == 0 || list[i].realtime < *oldest_usec))
322 *oldest_usec = list[i].realtime;
323
0284adc6
LP
324finish:
325 for (i = 0; i < n_list; i++)
326 free(list[i].filename);
0284adc6
LP
327 free(list);
328
dbd2a83f 329 log_full(verbose ? LOG_INFO : LOG_DEBUG, "Vacuuming done, freed %s of archived journals on disk.", format_bytes(sbytes, sizeof(sbytes), freed));
289f910e 330
0284adc6
LP
331 return r;
332}