1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
4 This file is part of systemd.
6 Copyright 2011 Lennart Poettering
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.
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.
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/>.
22 #include <sys/types.h>
25 #include <sys/statvfs.h>
29 #include <attr/xattr.h>
32 #include "journal-def.h"
33 #include "journal-file.h"
34 #include "journal-vacuum.h"
49 static int vacuum_compare(const void *_a, const void *_b) {
50 const struct vacuum_info *a, *b;
55 if (a->have_seqnum && b->have_seqnum &&
56 sd_id128_equal(a->seqnum_id, b->seqnum_id)) {
57 if (a->seqnum < b->seqnum)
59 else if (a->seqnum > b->seqnum)
65 if (a->realtime < b->realtime)
67 else if (a->realtime > b->realtime)
69 else if (a->have_seqnum && b->have_seqnum)
70 return memcmp(&a->seqnum_id, &b->seqnum_id, 16);
72 return strcmp(a->filename, b->filename);
75 static void patch_realtime(
78 const struct stat *st,
79 unsigned long long *realtime) {
85 _cleanup_free_ const char *path = NULL;
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
97 x = timespec_load(&st->st_ctim);
98 if (x > 0 && x != (usec_t) -1 && x < *realtime)
101 x = timespec_load(&st->st_atim);
102 if (x > 0 && x != (usec_t) -1 && x < *realtime)
105 x = timespec_load(&st->st_mtim);
106 if (x > 0 && x != (usec_t) -1 && x < *realtime)
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... */
115 /* Unfortunately there is is not fgetxattrat(), so we need to
116 * go via path here. :-( */
118 path = strjoin(dir, "/", fn, NULL);
122 if (getxattr(path, "user.crtime_usec", &crtime, sizeof(crtime)) == sizeof(crtime)) {
123 crtime = le64toh(crtime);
125 if (crtime > 0 && crtime != (uint64_t) -1 && crtime < *realtime)
131 int journal_directory_vacuum(
132 const char *directory,
135 usec_t max_retention_usec,
136 usec_t *oldest_usec) {
140 struct vacuum_info *list = NULL;
141 unsigned n_list = 0, n_allocated = 0, i;
143 usec_t retention_limit = 0;
147 if (max_use <= 0 && min_free <= 0 && max_retention_usec <= 0)
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;
155 max_retention_usec = retention_limit = 0;
158 d = opendir(directory);
165 union dirent_storage buf;
169 unsigned long long seqnum = 0, realtime;
170 sd_id128_t seqnum_id;
173 k = readdir_r(d, &buf.de, &de);
182 if (fstatat(dirfd(d), de->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0)
185 if (!S_ISREG(st.st_mode))
188 q = strlen(de->d_name);
190 if (endswith(de->d_name, ".journal")) {
192 /* Vacuum archived files */
194 if (q < 1 + 32 + 1 + 16 + 1 + 16 + 8)
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] != '@')
202 p = strdup(de->d_name);
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) {
214 if (sscanf(de->d_name + q-8-16-1-16, "%16llx-%16llx.journal", &seqnum, &realtime) != 2) {
221 } else if (endswith(de->d_name, ".journal~")) {
222 unsigned long long tmp;
224 /* Vacuum corrupted files */
226 if (q < 1 + 16 + 1 + 16 + 8 + 1)
229 if (de->d_name[q-1-8-16-1] != '-' ||
230 de->d_name[q-1-8-16-1-16-1] != '@')
233 p = strdup(de->d_name);
239 if (sscanf(de->d_name + q-1-8-16-1-16, "%16llx-%16llx.journal~", &realtime, &tmp) != 2) {
246 /* We do not vacuum active files or unknown files! */
249 patch_realtime(directory, de->d_name, &st, &realtime);
251 if (n_list >= n_allocated) {
252 struct vacuum_info *j;
254 n_allocated = MAX(n_allocated * 2U, 8U);
255 j = realloc(list, n_allocated * sizeof(struct vacuum_info));
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;
272 sum += list[n_list].usage;
278 qsort(list, n_list, sizeof(struct vacuum_info), vacuum_compare);
280 for (i = 0; i < n_list; i++) {
283 if (fstatvfs(dirfd(d), &ss) < 0) {
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))
293 if (unlinkat(dirfd(d), list[i].filename, 0) >= 0) {
294 log_debug("Deleted archived journal %s/%s.", directory, list[i].filename);
296 if ((uint64_t) list[i].usage > sum)
297 sum -= list[i].usage;
301 } else if (errno != ENOENT)
302 log_warning("Failed to delete %s/%s: %m", directory, list[i].filename);
305 if (oldest_usec && i < n_list && (*oldest_usec == 0 || list[i].realtime < *oldest_usec))
306 *oldest_usec = list[i].realtime;
309 for (i = 0; i < n_list; i++)
310 free(list[i].filename);