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