]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/journal/journal-vacuum.c
journal: fix potential integer overflow
[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 <sys/types.h>
23 #include <fcntl.h>
24 #include <sys/stat.h>
25 #include <sys/statvfs.h>
26 #include <unistd.h>
27
28 #ifdef HAVE_XATTR
29 #include <attr/xattr.h>
30 #endif
31
32 #include "journal-def.h"
33 #include "journal-file.h"
34 #include "journal-vacuum.h"
35 #include "sd-id128.h"
36 #include "util.h"
37
38 struct vacuum_info {
39 off_t usage;
40 char *filename;
41
42 uint64_t realtime;
43 sd_id128_t seqnum_id;
44 uint64_t seqnum;
45
46 bool have_seqnum;
47 };
48
49 static int vacuum_compare(const void *_a, const void *_b) {
50 const struct vacuum_info *a, *b;
51
52 a = _a;
53 b = _b;
54
55 if (a->have_seqnum && b->have_seqnum &&
56 sd_id128_equal(a->seqnum_id, b->seqnum_id)) {
57 if (a->seqnum < b->seqnum)
58 return -1;
59 else if (a->seqnum > b->seqnum)
60 return 1;
61 else
62 return 0;
63 }
64
65 if (a->realtime < b->realtime)
66 return -1;
67 else if (a->realtime > b->realtime)
68 return 1;
69 else if (a->have_seqnum && b->have_seqnum)
70 return memcmp(&a->seqnum_id, &b->seqnum_id, 16);
71 else
72 return strcmp(a->filename, b->filename);
73 }
74
75 static void patch_realtime(
76 const char *dir,
77 const char *fn,
78 const struct stat *st,
79 unsigned long long *realtime) {
80
81 usec_t x;
82
83 #ifdef HAVE_XATTR
84 uint64_t crtime;
85 _cleanup_free_ const char *path = NULL;
86 #endif
87
88 /* The timestamp was determined by the file name, but let's
89 * see if the file might actually be older than the file name
90 * suggested... */
91
92 assert(dir);
93 assert(fn);
94 assert(st);
95 assert(realtime);
96
97 x = timespec_load(&st->st_ctim);
98 if (x > 0 && x != (usec_t) -1 && x < *realtime)
99 *realtime = x;
100
101 x = timespec_load(&st->st_atim);
102 if (x > 0 && x != (usec_t) -1 && x < *realtime)
103 *realtime = x;
104
105 x = timespec_load(&st->st_mtim);
106 if (x > 0 && x != (usec_t) -1 && x < *realtime)
107 *realtime = x;
108
109 #ifdef HAVE_XATTR
110 /* Let's read the original creation time, if possible. Ideally
111 * we'd just query the creation time the FS might provide, but
112 * unfortunately there's currently no sane API to query
113 * it. Hence let's implement this manually... */
114
115 /* Unfortunately there is is not fgetxattrat(), so we need to
116 * go via path here. :-( */
117
118 path = strjoin(dir, "/", fn, NULL);
119 if (!path)
120 return;
121
122 if (getxattr(path, "user.crtime_usec", &crtime, sizeof(crtime)) == sizeof(crtime)) {
123 crtime = le64toh(crtime);
124
125 if (crtime > 0 && crtime != (uint64_t) -1 && crtime < *realtime)
126 *realtime = crtime;
127 }
128 #endif
129 }
130
131 int journal_directory_vacuum(
132 const char *directory,
133 uint64_t max_use,
134 uint64_t min_free,
135 usec_t max_retention_usec,
136 usec_t *oldest_usec) {
137
138 DIR *d;
139 int r = 0;
140 struct vacuum_info *list = NULL;
141 unsigned n_list = 0, n_allocated = 0, i;
142 uint64_t sum = 0;
143 usec_t retention_limit = 0;
144
145 assert(directory);
146
147 if (max_use <= 0 && min_free <= 0 && max_retention_usec <= 0)
148 return 0;
149
150 if (max_retention_usec > 0) {
151 retention_limit = now(CLOCK_REALTIME);
152 if (retention_limit > max_retention_usec)
153 retention_limit -= max_retention_usec;
154 else
155 max_retention_usec = retention_limit = 0;
156 }
157
158 d = opendir(directory);
159 if (!d)
160 return -errno;
161
162 for (;;) {
163 int k;
164 struct dirent *de;
165 union dirent_storage buf;
166 size_t q;
167 struct stat st;
168 char *p;
169 unsigned long long seqnum = 0, realtime;
170 sd_id128_t seqnum_id;
171 bool have_seqnum;
172
173 k = readdir_r(d, &buf.de, &de);
174 if (k != 0) {
175 r = -k;
176 goto finish;
177 }
178
179 if (!de)
180 break;
181
182 if (fstatat(dirfd(d), de->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0)
183 continue;
184
185 if (!S_ISREG(st.st_mode))
186 continue;
187
188 q = strlen(de->d_name);
189
190 if (endswith(de->d_name, ".journal")) {
191
192 /* Vacuum archived files */
193
194 if (q < 1 + 32 + 1 + 16 + 1 + 16 + 8)
195 continue;
196
197 if (de->d_name[q-8-16-1] != '-' ||
198 de->d_name[q-8-16-1-16-1] != '-' ||
199 de->d_name[q-8-16-1-16-1-32-1] != '@')
200 continue;
201
202 p = strdup(de->d_name);
203 if (!p) {
204 r = -ENOMEM;
205 goto finish;
206 }
207
208 de->d_name[q-8-16-1-16-1] = 0;
209 if (sd_id128_from_string(de->d_name + q-8-16-1-16-1-32, &seqnum_id) < 0) {
210 free(p);
211 continue;
212 }
213
214 if (sscanf(de->d_name + q-8-16-1-16, "%16llx-%16llx.journal", &seqnum, &realtime) != 2) {
215 free(p);
216 continue;
217 }
218
219 have_seqnum = true;
220
221 } else if (endswith(de->d_name, ".journal~")) {
222 unsigned long long tmp;
223
224 /* Vacuum corrupted files */
225
226 if (q < 1 + 16 + 1 + 16 + 8 + 1)
227 continue;
228
229 if (de->d_name[q-1-8-16-1] != '-' ||
230 de->d_name[q-1-8-16-1-16-1] != '@')
231 continue;
232
233 p = strdup(de->d_name);
234 if (!p) {
235 r = -ENOMEM;
236 goto finish;
237 }
238
239 if (sscanf(de->d_name + q-1-8-16-1-16, "%16llx-%16llx.journal~", &realtime, &tmp) != 2) {
240 free(p);
241 continue;
242 }
243
244 have_seqnum = false;
245 } else
246 /* We do not vacuum active files or unknown files! */
247 continue;
248
249 patch_realtime(directory, de->d_name, &st, &realtime);
250
251 if (n_list >= n_allocated) {
252 struct vacuum_info *j;
253
254 n_allocated = MAX(n_allocated * 2U, 8U);
255 j = realloc(list, n_allocated * sizeof(struct vacuum_info));
256 if (!j) {
257 free(p);
258 r = -ENOMEM;
259 goto finish;
260 }
261
262 list = j;
263 }
264
265 list[n_list].filename = p;
266 list[n_list].usage = 512UL * (uint64_t) st.st_blocks;
267 list[n_list].seqnum = seqnum;
268 list[n_list].realtime = realtime;
269 list[n_list].seqnum_id = seqnum_id;
270 list[n_list].have_seqnum = have_seqnum;
271
272 sum += list[n_list].usage;
273
274 n_list ++;
275 }
276
277 if (n_list > 0)
278 qsort(list, n_list, sizeof(struct vacuum_info), vacuum_compare);
279
280 for (i = 0; i < n_list; i++) {
281 struct statvfs ss;
282
283 if (fstatvfs(dirfd(d), &ss) < 0) {
284 r = -errno;
285 goto finish;
286 }
287
288 if ((max_retention_usec <= 0 || list[i].realtime >= retention_limit) &&
289 (max_use <= 0 || sum <= max_use) &&
290 (min_free <= 0 || (uint64_t) ss.f_bavail * (uint64_t) ss.f_bsize >= min_free))
291 break;
292
293 if (unlinkat(dirfd(d), list[i].filename, 0) >= 0) {
294 log_debug("Deleted archived journal %s/%s.", directory, list[i].filename);
295
296 if ((uint64_t) list[i].usage > sum)
297 sum -= list[i].usage;
298 else
299 sum = 0;
300
301 } else if (errno != ENOENT)
302 log_warning("Failed to delete %s/%s: %m", directory, list[i].filename);
303 }
304
305 if (oldest_usec && i < n_list && (*oldest_usec == 0 || list[i].realtime < *oldest_usec))
306 *oldest_usec = list[i].realtime;
307
308 finish:
309 for (i = 0; i < n_list; i++)
310 free(list[i].filename);
311
312 free(list);
313
314 if (d)
315 closedir(d);
316
317 return r;
318 }