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) {
78 _cleanup_free_ const char *path = NULL;
81 /* The timestamp was determined by the file name, but let's
82 * see if the file might actually be older than the file name
90 x = timespec_load(&st->st_ctim);
91 if (x > 0 && x != USEC_INFINITY && x < *realtime)
94 x = timespec_load(&st->st_atim);
95 if (x > 0 && x != USEC_INFINITY && x < *realtime)
98 x = timespec_load(&st->st_mtim);
99 if (x > 0 && x != USEC_INFINITY && x < *realtime)
102 /* Let's read the original creation time, if possible. Ideally
103 * we'd just query the creation time the FS might provide, but
104 * unfortunately there's currently no sane API to query
105 * it. Hence let's implement this manually... */
107 /* Unfortunately there is is not fgetxattrat(), so we need to
108 * go via path here. :-( */
110 path = strjoin(dir, "/", fn, NULL);
114 if (path_getcrtime(path, &crtime) >= 0) {
115 if (crtime < *realtime)
120 static int journal_file_empty(int dir_fd, const char *name) {
121 _cleanup_close_ int fd;
126 fd = openat(dir_fd, name, O_RDONLY|O_CLOEXEC|O_NOFOLLOW|O_NONBLOCK);
130 if (fstat(fd, &st) < 0)
133 /* If an offline file doesn't even have a header we consider it empty */
134 if (st.st_size < (off_t) sizeof(Header))
137 /* If the number of entries is empty, we consider it empty, too */
138 n = pread(fd, &n_entries, sizeof(n_entries), offsetof(Header, n_entries));
141 if (n != sizeof(n_entries))
144 return le64toh(n_entries) <= 0;
147 int journal_directory_vacuum(
148 const char *directory,
150 usec_t max_retention_usec,
154 _cleanup_closedir_ DIR *d = NULL;
156 struct vacuum_info *list = NULL;
157 unsigned n_list = 0, i;
158 size_t n_allocated = 0;
159 uint64_t sum = 0, freed = 0;
160 usec_t retention_limit = 0;
161 char sbytes[FORMAT_BYTES_MAX];
165 if (max_use <= 0 && max_retention_usec <= 0)
168 if (max_retention_usec > 0) {
169 retention_limit = now(CLOCK_REALTIME);
170 if (retention_limit > max_retention_usec)
171 retention_limit -= max_retention_usec;
173 max_retention_usec = retention_limit = 0;
176 d = opendir(directory);
185 unsigned long long seqnum = 0, realtime;
186 sd_id128_t seqnum_id;
191 if (!de && errno != 0) {
199 if (fstatat(dirfd(d), de->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0)
202 if (!S_ISREG(st.st_mode))
205 q = strlen(de->d_name);
207 if (endswith(de->d_name, ".journal")) {
209 /* Vacuum archived files */
211 if (q < 1 + 32 + 1 + 16 + 1 + 16 + 8)
214 if (de->d_name[q-8-16-1] != '-' ||
215 de->d_name[q-8-16-1-16-1] != '-' ||
216 de->d_name[q-8-16-1-16-1-32-1] != '@')
219 p = strdup(de->d_name);
225 de->d_name[q-8-16-1-16-1] = 0;
226 if (sd_id128_from_string(de->d_name + q-8-16-1-16-1-32, &seqnum_id) < 0) {
231 if (sscanf(de->d_name + q-8-16-1-16, "%16llx-%16llx.journal", &seqnum, &realtime) != 2) {
238 } else if (endswith(de->d_name, ".journal~")) {
239 unsigned long long tmp;
241 /* Vacuum corrupted files */
243 if (q < 1 + 16 + 1 + 16 + 8 + 1)
246 if (de->d_name[q-1-8-16-1] != '-' ||
247 de->d_name[q-1-8-16-1-16-1] != '@')
250 p = strdup(de->d_name);
256 if (sscanf(de->d_name + q-1-8-16-1-16, "%16llx-%16llx.journal~", &realtime, &tmp) != 2) {
263 /* We do not vacuum active files or unknown files! */
266 if (journal_file_empty(dirfd(d), p)) {
267 /* Always vacuum empty non-online files. */
269 uint64_t size = 512UL * (uint64_t) st.st_blocks;
271 if (unlinkat(dirfd(d), p, 0) >= 0) {
272 log_full(verbose ? LOG_INFO : LOG_DEBUG, "Deleted empty archived journal %s/%s (%s).", directory, p, format_bytes(sbytes, sizeof(sbytes), size));
274 } else if (errno != ENOENT)
275 log_warning_errno(errno, "Failed to delete empty archived journal %s/%s: %m", directory, p);
281 patch_realtime(directory, p, &st, &realtime);
283 if (!GREEDY_REALLOC(list, n_allocated, n_list + 1)) {
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;
296 sum += list[n_list].usage;
301 qsort_safe(list, n_list, sizeof(struct vacuum_info), vacuum_compare);
303 for (i = 0; i < n_list; i++) {
304 if ((max_retention_usec <= 0 || list[i].realtime >= retention_limit) &&
305 (max_use <= 0 || sum <= max_use))
308 if (unlinkat(dirfd(d), list[i].filename, 0) >= 0) {
309 log_full(verbose ? LOG_INFO : LOG_DEBUG, "Deleted archived journal %s/%s (%s).", directory, list[i].filename, format_bytes(sbytes, sizeof(sbytes), list[i].usage));
310 freed += list[i].usage;
312 if (list[i].usage < sum)
313 sum -= list[i].usage;
317 } else if (errno != ENOENT)
318 log_warning_errno(errno, "Failed to delete archived journal %s/%s: %m", directory, list[i].filename);
321 if (oldest_usec && i < n_list && (*oldest_usec == 0 || list[i].realtime < *oldest_usec))
322 *oldest_usec = list[i].realtime;
325 for (i = 0; i < n_list; i++)
326 free(list[i].filename);
329 log_full(verbose ? LOG_INFO : LOG_DEBUG, "Vacuuming done, freed %s of archived journals on disk.", format_bytes(sbytes, sizeof(sbytes), freed));