]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/journal/journal-vacuum.c
journal/vacuum: replace readdir_r with readdir
[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 uint64_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 static int journal_file_empty(int dir_fd, const char *name) {
132 int r;
133 le64_t n_entries;
134 _cleanup_close_ int fd;
135
136 fd = openat(dir_fd, name, O_RDONLY|O_CLOEXEC|O_NOFOLLOW|O_NONBLOCK);
137 if (fd < 0)
138 return -errno;
139
140 if (lseek(fd, offsetof(Header, n_entries), SEEK_SET) < 0)
141 return -errno;
142
143 r = read(fd, &n_entries, sizeof(n_entries));
144 if (r != sizeof(n_entries))
145 return r == 0 ? -EINVAL : -errno;
146
147 return le64toh(n_entries) == 0;
148 }
149
150 int journal_directory_vacuum(
151 const char *directory,
152 uint64_t max_use,
153 uint64_t min_free,
154 usec_t max_retention_usec,
155 usec_t *oldest_usec) {
156
157 _cleanup_closedir_ DIR *d = NULL;
158 int r = 0;
159 struct vacuum_info *list = NULL;
160 unsigned n_list = 0, i;
161 size_t n_allocated = 0;
162 uint64_t sum = 0, freed = 0;
163 usec_t retention_limit = 0;
164
165 assert(directory);
166
167 if (max_use <= 0 && min_free <= 0 && max_retention_usec <= 0)
168 return 0;
169
170 if (max_retention_usec > 0) {
171 retention_limit = now(CLOCK_REALTIME);
172 if (retention_limit > max_retention_usec)
173 retention_limit -= max_retention_usec;
174 else
175 max_retention_usec = retention_limit = 0;
176 }
177
178 d = opendir(directory);
179 if (!d)
180 return -errno;
181
182 for (;;) {
183 struct dirent *de;
184 size_t q;
185 struct stat st;
186 char *p;
187 unsigned long long seqnum = 0, realtime;
188 sd_id128_t seqnum_id;
189 bool have_seqnum;
190
191 errno = 0;
192 de = readdir(d);
193 if (!de && errno != 0) {
194 r = -errno;
195 goto finish;
196 }
197
198 if (!de)
199 break;
200
201 if (fstatat(dirfd(d), de->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0)
202 continue;
203
204 if (!S_ISREG(st.st_mode))
205 continue;
206
207 q = strlen(de->d_name);
208
209 if (endswith(de->d_name, ".journal")) {
210
211 /* Vacuum archived files */
212
213 if (q < 1 + 32 + 1 + 16 + 1 + 16 + 8)
214 continue;
215
216 if (de->d_name[q-8-16-1] != '-' ||
217 de->d_name[q-8-16-1-16-1] != '-' ||
218 de->d_name[q-8-16-1-16-1-32-1] != '@')
219 continue;
220
221 p = strdup(de->d_name);
222 if (!p) {
223 r = -ENOMEM;
224 goto finish;
225 }
226
227 de->d_name[q-8-16-1-16-1] = 0;
228 if (sd_id128_from_string(de->d_name + q-8-16-1-16-1-32, &seqnum_id) < 0) {
229 free(p);
230 continue;
231 }
232
233 if (sscanf(de->d_name + q-8-16-1-16, "%16llx-%16llx.journal", &seqnum, &realtime) != 2) {
234 free(p);
235 continue;
236 }
237
238 have_seqnum = true;
239
240 } else if (endswith(de->d_name, ".journal~")) {
241 unsigned long long tmp;
242
243 /* Vacuum corrupted files */
244
245 if (q < 1 + 16 + 1 + 16 + 8 + 1)
246 continue;
247
248 if (de->d_name[q-1-8-16-1] != '-' ||
249 de->d_name[q-1-8-16-1-16-1] != '@')
250 continue;
251
252 p = strdup(de->d_name);
253 if (!p) {
254 r = -ENOMEM;
255 goto finish;
256 }
257
258 if (sscanf(de->d_name + q-1-8-16-1-16, "%16llx-%16llx.journal~", &realtime, &tmp) != 2) {
259 free(p);
260 continue;
261 }
262
263 have_seqnum = false;
264 } else
265 /* We do not vacuum active files or unknown files! */
266 continue;
267
268 if (journal_file_empty(dirfd(d), p)) {
269 /* Always vacuum empty non-online files. */
270
271 uint64_t size = 512UL * (uint64_t) st.st_blocks;
272
273 if (unlinkat(dirfd(d), p, 0) >= 0) {
274 log_info("Deleted empty journal %s/%s (%"PRIu64" bytes).",
275 directory, p, size);
276 freed += size;
277 } else if (errno != ENOENT)
278 log_warning("Failed to delete %s/%s: %m", directory, p);
279
280 free(p);
281
282 continue;
283 }
284
285 patch_realtime(directory, p, &st, &realtime);
286
287 GREEDY_REALLOC(list, n_allocated, n_list + 1);
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
301 qsort_safe(list, n_list, sizeof(struct vacuum_info), vacuum_compare);
302
303 for (i = 0; i < n_list; i++) {
304 struct statvfs ss;
305
306 if (fstatvfs(dirfd(d), &ss) < 0) {
307 r = -errno;
308 goto finish;
309 }
310
311 if ((max_retention_usec <= 0 || list[i].realtime >= retention_limit) &&
312 (max_use <= 0 || sum <= max_use) &&
313 (min_free <= 0 || (uint64_t) ss.f_bavail * (uint64_t) ss.f_bsize >= min_free))
314 break;
315
316 if (unlinkat(dirfd(d), list[i].filename, 0) >= 0) {
317 log_debug("Deleted archived journal %s/%s (%"PRIu64" bytes).",
318 directory, list[i].filename, list[i].usage);
319 freed += list[i].usage;
320
321 if (list[i].usage < sum)
322 sum -= list[i].usage;
323 else
324 sum = 0;
325
326 } else if (errno != ENOENT)
327 log_warning("Failed to delete %s/%s: %m", directory, list[i].filename);
328 }
329
330 if (oldest_usec && i < n_list && (*oldest_usec == 0 || list[i].realtime < *oldest_usec))
331 *oldest_usec = list[i].realtime;
332
333 finish:
334 for (i = 0; i < n_list; i++)
335 free(list[i].filename);
336 free(list);
337
338 log_info("Vacuuming done, freed %"PRIu64" bytes", freed);
339
340 return r;
341 }