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/>.
28 #include "journal-def.h"
29 #include "journal-file.h"
30 #include "string-util.h"
33 #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 /* The timestamp was determined by the file name, but let's
81 * see if the file might actually be older than the file name
89 x
= timespec_load(&st
->st_ctim
);
90 if (x
> 0 && x
!= USEC_INFINITY
&& x
< *realtime
)
93 x
= timespec_load(&st
->st_atim
);
94 if (x
> 0 && x
!= USEC_INFINITY
&& x
< *realtime
)
97 x
= timespec_load(&st
->st_mtim
);
98 if (x
> 0 && x
!= USEC_INFINITY
&& x
< *realtime
)
101 /* Let's read the original creation time, if possible. Ideally
102 * we'd just query the creation time the FS might provide, but
103 * unfortunately there's currently no sane API to query
104 * it. Hence let's implement this manually... */
106 if (fd_getcrtime_at(fd
, fn
, &crtime
, 0) >= 0) {
107 if (crtime
< *realtime
)
112 static int journal_file_empty(int dir_fd
, const char *name
) {
113 _cleanup_close_
int fd
;
118 fd
= openat(dir_fd
, name
, O_RDONLY
|O_CLOEXEC
|O_NOFOLLOW
|O_NONBLOCK
|O_NOATIME
);
120 /* Maybe failed due to O_NOATIME and lack of privileges? */
121 fd
= openat(dir_fd
, name
, O_RDONLY
|O_CLOEXEC
|O_NOFOLLOW
|O_NONBLOCK
);
126 if (fstat(fd
, &st
) < 0)
129 /* If an offline file doesn't even have a header we consider it empty */
130 if (st
.st_size
< (off_t
) sizeof(Header
))
133 /* If the number of entries is empty, we consider it empty, too */
134 n
= pread(fd
, &n_entries
, sizeof(n_entries
), offsetof(Header
, n_entries
));
137 if (n
!= sizeof(n_entries
))
140 return le64toh(n_entries
) <= 0;
143 int journal_directory_vacuum(
144 const char *directory
,
146 uint64_t n_max_files
,
147 usec_t max_retention_usec
,
151 _cleanup_closedir_
DIR *d
= NULL
;
152 struct vacuum_info
*list
= NULL
;
153 unsigned n_list
= 0, i
, n_active_files
= 0;
154 size_t n_allocated
= 0;
155 uint64_t sum
= 0, freed
= 0;
156 usec_t retention_limit
= 0;
157 char sbytes
[FORMAT_BYTES_MAX
];
163 if (max_use
<= 0 && max_retention_usec
<= 0 && n_max_files
<= 0)
166 if (max_retention_usec
> 0) {
167 retention_limit
= now(CLOCK_REALTIME
);
168 if (retention_limit
> max_retention_usec
)
169 retention_limit
-= max_retention_usec
;
171 max_retention_usec
= retention_limit
= 0;
174 d
= opendir(directory
);
178 FOREACH_DIRENT_ALL(de
, d
, r
= -errno
; goto finish
) {
180 unsigned long long seqnum
= 0, realtime
;
181 _cleanup_free_
char *p
= NULL
;
182 sd_id128_t seqnum_id
;
188 if (fstatat(dirfd(d
), de
->d_name
, &st
, AT_SYMLINK_NOFOLLOW
) < 0) {
189 log_debug_errno(errno
, "Failed to stat file %s while vacuuming, ignoring: %m", de
->d_name
);
193 if (!S_ISREG(st
.st_mode
))
196 q
= strlen(de
->d_name
);
198 if (endswith(de
->d_name
, ".journal")) {
200 /* Vacuum archived files. Active files are
203 if (q
< 1 + 32 + 1 + 16 + 1 + 16 + 8) {
208 if (de
->d_name
[q
-8-16-1] != '-' ||
209 de
->d_name
[q
-8-16-1-16-1] != '-' ||
210 de
->d_name
[q
-8-16-1-16-1-32-1] != '@') {
215 p
= strdup(de
->d_name
);
221 de
->d_name
[q
-8-16-1-16-1] = 0;
222 if (sd_id128_from_string(de
->d_name
+ q
-8-16-1-16-1-32, &seqnum_id
) < 0) {
227 if (sscanf(de
->d_name
+ q
-8-16-1-16, "%16llx-%16llx.journal", &seqnum
, &realtime
) != 2) {
234 } else if (endswith(de
->d_name
, ".journal~")) {
235 unsigned long long tmp
;
237 /* Vacuum corrupted files */
239 if (q
< 1 + 16 + 1 + 16 + 8 + 1) {
244 if (de
->d_name
[q
-1-8-16-1] != '-' ||
245 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 unknown files! */
264 log_debug("Not vacuuming unknown file %s.", de
->d_name
);
268 size
= 512UL * (uint64_t) st
.st_blocks
;
270 r
= journal_file_empty(dirfd(d
), p
);
272 log_debug_errno(r
, "Failed check if %s is empty, ignoring: %m", p
);
276 /* Always vacuum empty non-online files. */
278 if (unlinkat(dirfd(d
), p
, 0) >= 0) {
280 log_full(verbose
? LOG_INFO
: LOG_DEBUG
,
281 "Deleted empty archived journal %s/%s (%s).", directory
, p
, format_bytes(sbytes
, sizeof(sbytes
), size
));
284 } else if (errno
!= ENOENT
)
285 log_warning_errno(errno
, "Failed to delete empty archived journal %s/%s: %m", directory
, p
);
290 patch_realtime(dirfd(d
), p
, &st
, &realtime
);
292 if (!GREEDY_REALLOC(list
, n_allocated
, n_list
+ 1)) {
297 list
[n_list
].filename
= p
;
298 list
[n_list
].usage
= size
;
299 list
[n_list
].seqnum
= seqnum
;
300 list
[n_list
].realtime
= realtime
;
301 list
[n_list
].seqnum_id
= seqnum_id
;
302 list
[n_list
].have_seqnum
= have_seqnum
;
309 qsort_safe(list
, n_list
, sizeof(struct vacuum_info
), vacuum_compare
);
311 for (i
= 0; i
< n_list
; i
++) {
314 left
= n_active_files
+ n_list
- i
;
316 if ((max_retention_usec
<= 0 || list
[i
].realtime
>= retention_limit
) &&
317 (max_use
<= 0 || sum
<= max_use
) &&
318 (n_max_files
<= 0 || left
<= n_max_files
))
321 if (unlinkat(dirfd(d
), list
[i
].filename
, 0) >= 0) {
322 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
));
323 freed
+= list
[i
].usage
;
325 if (list
[i
].usage
< sum
)
326 sum
-= list
[i
].usage
;
330 } else if (errno
!= ENOENT
)
331 log_warning_errno(errno
, "Failed to delete archived journal %s/%s: %m", directory
, list
[i
].filename
);
334 if (oldest_usec
&& i
< n_list
&& (*oldest_usec
== 0 || list
[i
].realtime
< *oldest_usec
))
335 *oldest_usec
= list
[i
].realtime
;
340 for (i
= 0; i
< n_list
; i
++)
341 free(list
[i
].filename
);
344 log_full(verbose
? LOG_INFO
: LOG_DEBUG
, "Vacuuming done, freed %s of archived journals on disk.", format_bytes(sbytes
, sizeof(sbytes
), freed
));