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,
149 _cleanup_closedir_ DIR *d = NULL;
151 struct vacuum_info *list = NULL;
152 unsigned n_list = 0, i;
153 size_t n_allocated = 0;
154 uint64_t sum = 0, freed = 0;
155 usec_t retention_limit = 0;
156 char sbytes[FORMAT_BYTES_MAX];
160 if (max_use <= 0 && max_retention_usec <= 0)
163 if (max_retention_usec > 0) {
164 retention_limit = now(CLOCK_REALTIME);
165 if (retention_limit > max_retention_usec)
166 retention_limit -= max_retention_usec;
168 max_retention_usec = retention_limit = 0;
171 d = opendir(directory);
180 unsigned long long seqnum = 0, realtime;
181 sd_id128_t seqnum_id;
186 if (!de && errno != 0) {
194 if (fstatat(dirfd(d), de->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0)
197 if (!S_ISREG(st.st_mode))
200 q = strlen(de->d_name);
202 if (endswith(de->d_name, ".journal")) {
204 /* Vacuum archived files */
206 if (q < 1 + 32 + 1 + 16 + 1 + 16 + 8)
209 if (de->d_name[q-8-16-1] != '-' ||
210 de->d_name[q-8-16-1-16-1] != '-' ||
211 de->d_name[q-8-16-1-16-1-32-1] != '@')
214 p = strdup(de->d_name);
220 de->d_name[q-8-16-1-16-1] = 0;
221 if (sd_id128_from_string(de->d_name + q-8-16-1-16-1-32, &seqnum_id) < 0) {
226 if (sscanf(de->d_name + q-8-16-1-16, "%16llx-%16llx.journal", &seqnum, &realtime) != 2) {
233 } else if (endswith(de->d_name, ".journal~")) {
234 unsigned long long tmp;
236 /* Vacuum corrupted files */
238 if (q < 1 + 16 + 1 + 16 + 8 + 1)
241 if (de->d_name[q-1-8-16-1] != '-' ||
242 de->d_name[q-1-8-16-1-16-1] != '@')
245 p = strdup(de->d_name);
251 if (sscanf(de->d_name + q-1-8-16-1-16, "%16llx-%16llx.journal~", &realtime, &tmp) != 2) {
258 /* We do not vacuum active files or unknown files! */
261 if (journal_file_empty(dirfd(d), p)) {
262 /* Always vacuum empty non-online files. */
264 uint64_t size = 512UL * (uint64_t) st.st_blocks;
266 if (unlinkat(dirfd(d), p, 0) >= 0) {
267 log_full(verbose ? LOG_INFO : LOG_DEBUG, "Deleted empty archived journal %s/%s (%s).", directory, p, format_bytes(sbytes, sizeof(sbytes), size));
269 } else if (errno != ENOENT)
270 log_warning("Failed to delete empty archived journal %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_full(verbose ? LOG_INFO : LOG_DEBUG, "Deleted archived journal %s/%s (%s).", directory, list[i].filename, format_bytes(sbytes, sizeof(sbytes), list[i].usage));
301 freed += list[i].usage;
303 if (list[i].usage < sum)
304 sum -= list[i].usage;
308 } else if (errno != ENOENT)
309 log_warning("Failed to delete archived journal %s/%s: %m", directory, list[i].filename);
312 if (oldest_usec && i < n_list && (*oldest_usec == 0 || list[i].realtime < *oldest_usec))
313 *oldest_usec = list[i].realtime;
316 for (i = 0; i < n_list; i++)
317 free(list[i].filename);
320 log_full(verbose ? LOG_INFO : LOG_DEBUG, "Vacuuming done, freed %s of archived journals on disk.", format_bytes(sbytes, sizeof(sbytes), freed));