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 static int journal_file_empty(int dir_fd, const char *name) {
135 fd = openat(dir_fd, name, O_RDONLY|O_CLOEXEC|O_NOFOLLOW|O_NONBLOCK);
139 if (lseek(fd, offsetof(Header, n_entries), SEEK_SET) < 0)
142 r = read(fd, &n_entries, sizeof(n_entries));
143 if (r != sizeof(n_entries))
144 return r == 0 ? -EINVAL : -errno;
146 return le64toh(n_entries) == 0;
149 int journal_directory_vacuum(
150 const char *directory,
153 usec_t max_retention_usec,
154 usec_t *oldest_usec) {
156 _cleanup_closedir_ DIR *d = NULL;
158 struct vacuum_info *list = NULL;
159 unsigned n_list = 0, i;
160 size_t n_allocated = 0;
162 usec_t retention_limit = 0;
166 if (max_use <= 0 && min_free <= 0 && max_retention_usec <= 0)
169 if (max_retention_usec > 0) {
170 retention_limit = now(CLOCK_REALTIME);
171 if (retention_limit > max_retention_usec)
172 retention_limit -= max_retention_usec;
174 max_retention_usec = retention_limit = 0;
177 d = opendir(directory);
184 union dirent_storage buf;
188 unsigned long long seqnum = 0, realtime;
189 sd_id128_t seqnum_id;
192 k = readdir_r(d, &buf.de, &de);
201 if (fstatat(dirfd(d), de->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0)
204 if (!S_ISREG(st.st_mode))
207 q = strlen(de->d_name);
209 if (endswith(de->d_name, ".journal")) {
211 /* Vacuum archived files */
213 if (q < 1 + 32 + 1 + 16 + 1 + 16 + 8)
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] != '@')
221 p = strdup(de->d_name);
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) {
233 if (sscanf(de->d_name + q-8-16-1-16, "%16llx-%16llx.journal", &seqnum, &realtime) != 2) {
240 } else if (endswith(de->d_name, ".journal~")) {
241 unsigned long long tmp;
243 /* Vacuum corrupted files */
245 if (q < 1 + 16 + 1 + 16 + 8 + 1)
248 if (de->d_name[q-1-8-16-1] != '-' ||
249 de->d_name[q-1-8-16-1-16-1] != '@')
252 p = strdup(de->d_name);
258 if (sscanf(de->d_name + q-1-8-16-1-16, "%16llx-%16llx.journal~", &realtime, &tmp) != 2) {
265 /* We do not vacuum active files or unknown files! */
268 if (journal_file_empty(dirfd(d), de->d_name)) {
270 /* Always vacuum empty non-online files. */
272 if (unlinkat(dirfd(d), de->d_name, 0) >= 0)
273 log_debug("Deleted empty journal %s/%s.", directory, de->d_name);
274 else if (errno != ENOENT)
275 log_warning("Failed to delete %s/%s: %m", directory, de->d_name);
279 patch_realtime(directory, de->d_name, &st, &realtime);
281 GREEDY_REALLOC(list, n_allocated, n_list + 1);
283 list[n_list].filename = p;
284 list[n_list].usage = 512UL * (uint64_t) st.st_blocks;
285 list[n_list].seqnum = seqnum;
286 list[n_list].realtime = realtime;
287 list[n_list].seqnum_id = seqnum_id;
288 list[n_list].have_seqnum = have_seqnum;
290 sum += list[n_list].usage;
296 qsort(list, n_list, sizeof(struct vacuum_info), vacuum_compare);
298 for (i = 0; i < n_list; i++) {
301 if (fstatvfs(dirfd(d), &ss) < 0) {
306 if ((max_retention_usec <= 0 || list[i].realtime >= retention_limit) &&
307 (max_use <= 0 || sum <= max_use) &&
308 (min_free <= 0 || (uint64_t) ss.f_bavail * (uint64_t) ss.f_bsize >= min_free))
311 if (unlinkat(dirfd(d), list[i].filename, 0) >= 0) {
312 log_debug("Deleted archived journal %s/%s.", directory, list[i].filename);
314 if (list[i].usage < sum)
315 sum -= list[i].usage;
319 } else if (errno != ENOENT)
320 log_warning("Failed to delete %s/%s: %m", directory, list[i].filename);
323 if (oldest_usec && i < n_list && (*oldest_usec == 0 || list[i].realtime < *oldest_usec))
324 *oldest_usec = list[i].realtime;
327 for (i = 0; i < n_list; i++)
328 free(list[i].filename);