1 /* SPDX-License-Identifier: LGPL-2.1+ */
3 This file is part of systemd.
5 Copyright 2011 Lennart Poettering
7 systemd is free software; you can redistribute it and/or modify it
8 under the terms of the GNU Lesser General Public License as published by
9 the Free Software Foundation; either version 2.1 of the License, or
10 (at your option) any later version.
12 systemd is distributed in the hope that it will be useful, but
13 WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 Lesser General Public License for more details.
17 You should have received a copy of the GNU Lesser General Public License
18 along with systemd; If not, see <http://www.gnu.org/licenses/>.
27 #include "alloc-util.h"
28 #include "dirent-util.h"
30 #include "journal-def.h"
31 #include "journal-file.h"
32 #include "journal-vacuum.h"
33 #include "parse-util.h"
34 #include "string-util.h"
36 #include "xattr-util.h"
49 static int vacuum_compare(const void *_a
, const void *_b
) {
50 const struct vacuum_info
*a
, *b
;
55 if (a
->have_seqnum
&& b
->have_seqnum
&&
56 sd_id128_equal(a
->seqnum_id
, b
->seqnum_id
)) {
57 if (a
->seqnum
< b
->seqnum
)
59 else if (a
->seqnum
> b
->seqnum
)
65 if (a
->realtime
< b
->realtime
)
67 else if (a
->realtime
> b
->realtime
)
69 else if (a
->have_seqnum
&& b
->have_seqnum
)
70 return memcmp(&a
->seqnum_id
, &b
->seqnum_id
, 16);
72 return strcmp(a
->filename
, b
->filename
);
75 static void patch_realtime(
78 const struct stat
*st
,
79 unsigned long long *realtime
) {
83 /* The timestamp was determined by the file name, but let's
84 * see if the file might actually be older than the file name
92 x
= timespec_load(&st
->st_ctim
);
93 if (x
> 0 && x
!= USEC_INFINITY
&& x
< *realtime
)
96 x
= timespec_load(&st
->st_atim
);
97 if (x
> 0 && x
!= USEC_INFINITY
&& x
< *realtime
)
100 x
= timespec_load(&st
->st_mtim
);
101 if (x
> 0 && x
!= USEC_INFINITY
&& x
< *realtime
)
104 /* Let's read the original creation time, if possible. Ideally
105 * we'd just query the creation time the FS might provide, but
106 * unfortunately there's currently no sane API to query
107 * it. Hence let's implement this manually... */
109 if (fd_getcrtime_at(fd
, fn
, &crtime
, 0) >= 0) {
110 if (crtime
< *realtime
)
115 static int journal_file_empty(int dir_fd
, const char *name
) {
116 _cleanup_close_
int fd
;
121 fd
= openat(dir_fd
, name
, O_RDONLY
|O_CLOEXEC
|O_NOFOLLOW
|O_NONBLOCK
|O_NOATIME
);
123 /* Maybe failed due to O_NOATIME and lack of privileges? */
124 fd
= openat(dir_fd
, name
, O_RDONLY
|O_CLOEXEC
|O_NOFOLLOW
|O_NONBLOCK
);
129 if (fstat(fd
, &st
) < 0)
132 /* If an offline file doesn't even have a header we consider it empty */
133 if (st
.st_size
< (off_t
) sizeof(Header
))
136 /* If the number of entries is empty, we consider it empty, too */
137 n
= pread(fd
, &n_entries
, sizeof(n_entries
), offsetof(Header
, n_entries
));
140 if (n
!= sizeof(n_entries
))
143 return le64toh(n_entries
) <= 0;
146 int journal_directory_vacuum(
147 const char *directory
,
149 uint64_t n_max_files
,
150 usec_t max_retention_usec
,
154 _cleanup_closedir_
DIR *d
= NULL
;
155 struct vacuum_info
*list
= NULL
;
156 unsigned n_list
= 0, i
, n_active_files
= 0;
157 size_t n_allocated
= 0;
158 uint64_t sum
= 0, freed
= 0;
159 usec_t retention_limit
= 0;
160 char sbytes
[FORMAT_BYTES_MAX
];
166 if (max_use
<= 0 && max_retention_usec
<= 0 && n_max_files
<= 0)
169 if (max_retention_usec
> 0) {
170 retention_limit
= now(CLOCK_REALTIME
);
171 if (retention_limit
> max_retention_usec
)
172 retention_limit
-= max_retention_usec
;
174 max_retention_usec
= retention_limit
= 0;
177 d
= opendir(directory
);
181 FOREACH_DIRENT_ALL(de
, d
, r
= -errno
; goto finish
) {
183 unsigned long long seqnum
= 0, realtime
;
184 _cleanup_free_
char *p
= NULL
;
185 sd_id128_t seqnum_id
;
191 if (fstatat(dirfd(d
), de
->d_name
, &st
, AT_SYMLINK_NOFOLLOW
) < 0) {
192 log_debug_errno(errno
, "Failed to stat file %s while vacuuming, ignoring: %m", de
->d_name
);
196 if (!S_ISREG(st
.st_mode
))
199 q
= strlen(de
->d_name
);
201 if (endswith(de
->d_name
, ".journal")) {
203 /* Vacuum archived files. Active files are
206 if (q
< 1 + 32 + 1 + 16 + 1 + 16 + 8) {
211 if (de
->d_name
[q
-8-16-1] != '-' ||
212 de
->d_name
[q
-8-16-1-16-1] != '-' ||
213 de
->d_name
[q
-8-16-1-16-1-32-1] != '@') {
218 p
= strdup(de
->d_name
);
224 de
->d_name
[q
-8-16-1-16-1] = 0;
225 if (sd_id128_from_string(de
->d_name
+ q
-8-16-1-16-1-32, &seqnum_id
) < 0) {
230 if (sscanf(de
->d_name
+ q
-8-16-1-16, "%16llx-%16llx.journal", &seqnum
, &realtime
) != 2) {
237 } else if (endswith(de
->d_name
, ".journal~")) {
238 unsigned long long tmp
;
240 /* Vacuum corrupted files */
242 if (q
< 1 + 16 + 1 + 16 + 8 + 1) {
247 if (de
->d_name
[q
-1-8-16-1] != '-' ||
248 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 unknown files! */
267 log_debug("Not vacuuming unknown file %s.", de
->d_name
);
271 size
= 512UL * (uint64_t) st
.st_blocks
;
273 r
= journal_file_empty(dirfd(d
), p
);
275 log_debug_errno(r
, "Failed check if %s is empty, ignoring: %m", p
);
279 /* Always vacuum empty non-online files. */
281 if (unlinkat(dirfd(d
), p
, 0) >= 0) {
283 log_full(verbose
? LOG_INFO
: LOG_DEBUG
,
284 "Deleted empty archived journal %s/%s (%s).", directory
, p
, format_bytes(sbytes
, sizeof(sbytes
), size
));
287 } else if (errno
!= ENOENT
)
288 log_warning_errno(errno
, "Failed to delete empty archived journal %s/%s: %m", directory
, p
);
293 patch_realtime(dirfd(d
), p
, &st
, &realtime
);
295 if (!GREEDY_REALLOC(list
, n_allocated
, n_list
+ 1)) {
300 list
[n_list
].filename
= p
;
301 list
[n_list
].usage
= size
;
302 list
[n_list
].seqnum
= seqnum
;
303 list
[n_list
].realtime
= realtime
;
304 list
[n_list
].seqnum_id
= seqnum_id
;
305 list
[n_list
].have_seqnum
= have_seqnum
;
312 qsort_safe(list
, n_list
, sizeof(struct vacuum_info
), vacuum_compare
);
314 for (i
= 0; i
< n_list
; i
++) {
317 left
= n_active_files
+ n_list
- i
;
319 if ((max_retention_usec
<= 0 || list
[i
].realtime
>= retention_limit
) &&
320 (max_use
<= 0 || sum
<= max_use
) &&
321 (n_max_files
<= 0 || left
<= n_max_files
))
324 if (unlinkat(dirfd(d
), list
[i
].filename
, 0) >= 0) {
325 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
));
326 freed
+= list
[i
].usage
;
328 if (list
[i
].usage
< sum
)
329 sum
-= list
[i
].usage
;
333 } else if (errno
!= ENOENT
)
334 log_warning_errno(errno
, "Failed to delete archived journal %s/%s: %m", directory
, list
[i
].filename
);
337 if (oldest_usec
&& i
< n_list
&& (*oldest_usec
== 0 || list
[i
].realtime
< *oldest_usec
))
338 *oldest_usec
= list
[i
].realtime
;
343 for (i
= 0; i
< n_list
; i
++)
344 free(list
[i
].filename
);
347 log_full(verbose
? LOG_INFO
: LOG_DEBUG
, "Vacuuming done, freed %s of archived journals from %s.", format_bytes(sbytes
, sizeof(sbytes
), freed
), directory
);