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>
27 #include <sys/xattr.h>
29 #include "journal-def.h"
30 #include "journal-file.h"
31 #include "journal-vacuum.h"
46 static int vacuum_compare(const void *_a, const void *_b) {
47 const struct vacuum_info *a, *b;
52 if (a->have_seqnum && b->have_seqnum &&
53 sd_id128_equal(a->seqnum_id, b->seqnum_id)) {
54 if (a->seqnum < b->seqnum)
56 else if (a->seqnum > b->seqnum)
62 if (a->realtime < b->realtime)
64 else if (a->realtime > b->realtime)
66 else if (a->have_seqnum && b->have_seqnum)
67 return memcmp(&a->seqnum_id, &b->seqnum_id, 16);
69 return strcmp(a->filename, b->filename);
72 static void patch_realtime(
75 const struct stat *st,
76 unsigned long long *realtime) {
80 _cleanup_free_ const char *path = NULL;
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
91 x = timespec_load(&st->st_ctim);
92 if (x > 0 && x != USEC_INFINITY && x < *realtime)
95 x = timespec_load(&st->st_atim);
96 if (x > 0 && x != USEC_INFINITY && x < *realtime)
99 x = timespec_load(&st->st_mtim);
100 if (x > 0 && x != USEC_INFINITY && x < *realtime)
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... */
108 /* Unfortunately there is is not fgetxattrat(), so we need to
109 * go via path here. :-( */
111 path = strjoin(dir, "/", fn, NULL);
115 if (getxattr(path, "user.crtime_usec", &crtime, sizeof(crtime)) == sizeof(crtime)) {
116 crtime = le64toh(crtime);
118 if (crtime > 0 && crtime != (uint64_t) -1 && crtime < *realtime)
123 static int journal_file_empty(int dir_fd, const char *name) {
126 _cleanup_close_ int fd;
128 fd = openat(dir_fd, name, O_RDONLY|O_CLOEXEC|O_NOFOLLOW|O_NONBLOCK);
132 if (lseek(fd, offsetof(Header, n_entries), SEEK_SET) < 0)
135 r = read(fd, &n_entries, sizeof(n_entries));
136 if (r != sizeof(n_entries))
137 return r == 0 ? -EINVAL : -errno;
139 return le64toh(n_entries) == 0;
142 int journal_directory_vacuum(
143 const char *directory,
145 usec_t max_retention_usec,
146 usec_t *oldest_usec) {
148 _cleanup_closedir_ DIR *d = NULL;
150 struct vacuum_info *list = NULL;
151 unsigned n_list = 0, i;
152 size_t n_allocated = 0;
153 uint64_t sum = 0, freed = 0;
154 usec_t retention_limit = 0;
158 if (max_use <= 0 && max_retention_usec <= 0)
161 if (max_retention_usec > 0) {
162 retention_limit = now(CLOCK_REALTIME);
163 if (retention_limit > max_retention_usec)
164 retention_limit -= max_retention_usec;
166 max_retention_usec = retention_limit = 0;
169 d = opendir(directory);
178 unsigned long long seqnum = 0, realtime;
179 sd_id128_t seqnum_id;
184 if (!de && errno != 0) {
192 if (fstatat(dirfd(d), de->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0)
195 if (!S_ISREG(st.st_mode))
198 q = strlen(de->d_name);
200 if (endswith(de->d_name, ".journal")) {
202 /* Vacuum archived files */
204 if (q < 1 + 32 + 1 + 16 + 1 + 16 + 8)
207 if (de->d_name[q-8-16-1] != '-' ||
208 de->d_name[q-8-16-1-16-1] != '-' ||
209 de->d_name[q-8-16-1-16-1-32-1] != '@')
212 p = strdup(de->d_name);
218 de->d_name[q-8-16-1-16-1] = 0;
219 if (sd_id128_from_string(de->d_name + q-8-16-1-16-1-32, &seqnum_id) < 0) {
224 if (sscanf(de->d_name + q-8-16-1-16, "%16llx-%16llx.journal", &seqnum, &realtime) != 2) {
231 } else if (endswith(de->d_name, ".journal~")) {
232 unsigned long long tmp;
234 /* Vacuum corrupted files */
236 if (q < 1 + 16 + 1 + 16 + 8 + 1)
239 if (de->d_name[q-1-8-16-1] != '-' ||
240 de->d_name[q-1-8-16-1-16-1] != '@')
243 p = strdup(de->d_name);
249 if (sscanf(de->d_name + q-1-8-16-1-16, "%16llx-%16llx.journal~", &realtime, &tmp) != 2) {
256 /* We do not vacuum active files or unknown files! */
259 if (journal_file_empty(dirfd(d), p)) {
260 /* Always vacuum empty non-online files. */
262 uint64_t size = 512UL * (uint64_t) st.st_blocks;
264 if (unlinkat(dirfd(d), p, 0) >= 0) {
265 log_info("Deleted empty journal %s/%s (%"PRIu64" bytes).",
268 } else if (errno != ENOENT)
269 log_warning("Failed to delete %s/%s: %m", directory, p);
276 patch_realtime(directory, p, &st, &realtime);
278 GREEDY_REALLOC(list, n_allocated, n_list + 1);
280 list[n_list].filename = p;
281 list[n_list].usage = 512UL * (uint64_t) st.st_blocks;
282 list[n_list].seqnum = seqnum;
283 list[n_list].realtime = realtime;
284 list[n_list].seqnum_id = seqnum_id;
285 list[n_list].have_seqnum = have_seqnum;
287 sum += list[n_list].usage;
292 qsort_safe(list, n_list, sizeof(struct vacuum_info), vacuum_compare);
294 for (i = 0; i < n_list; i++) {
295 if ((max_retention_usec <= 0 || list[i].realtime >= retention_limit) &&
296 (max_use <= 0 || sum <= max_use))
299 if (unlinkat(dirfd(d), list[i].filename, 0) >= 0) {
300 log_debug("Deleted archived journal %s/%s (%"PRIu64" bytes).",
301 directory, list[i].filename, list[i].usage);
302 freed += list[i].usage;
304 if (list[i].usage < sum)
305 sum -= list[i].usage;
309 } else if (errno != ENOENT)
310 log_warning("Failed to delete %s/%s: %m", directory, list[i].filename);
313 if (oldest_usec && i < n_list && (*oldest_usec == 0 || list[i].realtime < *oldest_usec))
314 *oldest_usec = list[i].realtime;
317 for (i = 0; i < n_list; i++)
318 free(list[i].filename);
321 log_debug("Vacuuming done, freed %"PRIu64" bytes", freed);