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) {
124 _cleanup_close_ int fd;
129 fd = openat(dir_fd, name, O_RDONLY|O_CLOEXEC|O_NOFOLLOW|O_NONBLOCK);
133 if (fstat(fd, &st) < 0)
136 /* If an offline file doesn't even have a header we consider it empty */
137 if (st.st_size < (off_t) sizeof(Header))
140 /* If the number of entries is empty, we consider it empty, too */
141 n = pread(fd, &n_entries, sizeof(n_entries), offsetof(Header, n_entries));
144 if (n != sizeof(n_entries))
147 return le64toh(n_entries) <= 0;
150 int journal_directory_vacuum(
151 const char *directory,
153 usec_t max_retention_usec,
157 _cleanup_closedir_ DIR *d = NULL;
159 struct vacuum_info *list = NULL;
160 unsigned n_list = 0, i;
161 size_t n_allocated = 0;
162 uint64_t sum = 0, freed = 0;
163 usec_t retention_limit = 0;
164 char sbytes[FORMAT_BYTES_MAX];
168 if (max_use <= 0 && max_retention_usec <= 0)
171 if (max_retention_usec > 0) {
172 retention_limit = now(CLOCK_REALTIME);
173 if (retention_limit > max_retention_usec)
174 retention_limit -= max_retention_usec;
176 max_retention_usec = retention_limit = 0;
179 d = opendir(directory);
188 unsigned long long seqnum = 0, realtime;
189 sd_id128_t seqnum_id;
194 if (!de && errno != 0) {
202 if (fstatat(dirfd(d), de->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0)
205 if (!S_ISREG(st.st_mode))
208 q = strlen(de->d_name);
210 if (endswith(de->d_name, ".journal")) {
212 /* Vacuum archived files */
214 if (q < 1 + 32 + 1 + 16 + 1 + 16 + 8)
217 if (de->d_name[q-8-16-1] != '-' ||
218 de->d_name[q-8-16-1-16-1] != '-' ||
219 de->d_name[q-8-16-1-16-1-32-1] != '@')
222 p = strdup(de->d_name);
228 de->d_name[q-8-16-1-16-1] = 0;
229 if (sd_id128_from_string(de->d_name + q-8-16-1-16-1-32, &seqnum_id) < 0) {
234 if (sscanf(de->d_name + q-8-16-1-16, "%16llx-%16llx.journal", &seqnum, &realtime) != 2) {
241 } else if (endswith(de->d_name, ".journal~")) {
242 unsigned long long tmp;
244 /* Vacuum corrupted files */
246 if (q < 1 + 16 + 1 + 16 + 8 + 1)
249 if (de->d_name[q-1-8-16-1] != '-' ||
250 de->d_name[q-1-8-16-1-16-1] != '@')
253 p = strdup(de->d_name);
259 if (sscanf(de->d_name + q-1-8-16-1-16, "%16llx-%16llx.journal~", &realtime, &tmp) != 2) {
266 /* We do not vacuum active files or unknown files! */
269 if (journal_file_empty(dirfd(d), p)) {
270 /* Always vacuum empty non-online files. */
272 uint64_t size = 512UL * (uint64_t) st.st_blocks;
274 if (unlinkat(dirfd(d), p, 0) >= 0) {
275 log_full(verbose ? LOG_INFO : LOG_DEBUG, "Deleted empty archived journal %s/%s (%s).", directory, p, format_bytes(sbytes, sizeof(sbytes), size));
277 } else if (errno != ENOENT)
278 log_warning("Failed to delete empty archived journal %s/%s: %m", directory, p);
284 patch_realtime(directory, p, &st, &realtime);
286 if (!GREEDY_REALLOC(list, n_allocated, n_list + 1)) {
292 list[n_list].filename = p;
293 list[n_list].usage = 512UL * (uint64_t) st.st_blocks;
294 list[n_list].seqnum = seqnum;
295 list[n_list].realtime = realtime;
296 list[n_list].seqnum_id = seqnum_id;
297 list[n_list].have_seqnum = have_seqnum;
299 sum += list[n_list].usage;
304 qsort_safe(list, n_list, sizeof(struct vacuum_info), vacuum_compare);
306 for (i = 0; i < n_list; i++) {
307 if ((max_retention_usec <= 0 || list[i].realtime >= retention_limit) &&
308 (max_use <= 0 || sum <= max_use))
311 if (unlinkat(dirfd(d), list[i].filename, 0) >= 0) {
312 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));
313 freed += list[i].usage;
315 if (list[i].usage < sum)
316 sum -= list[i].usage;
320 } else if (errno != ENOENT)
321 log_warning("Failed to delete archived journal %s/%s: %m", directory, list[i].filename);
324 if (oldest_usec && i < n_list && (*oldest_usec == 0 || list[i].realtime < *oldest_usec))
325 *oldest_usec = list[i].realtime;
328 for (i = 0; i < n_list; i++)
329 free(list[i].filename);
332 log_full(verbose ? LOG_INFO : LOG_DEBUG, "Vacuuming done, freed %s of archived journals on disk.", format_bytes(sbytes, sizeof(sbytes), freed));