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"
31 #include "journal-def.h"
32 #include "journal-file.h"
33 #include "journal-vacuum.h"
34 #include "parse-util.h"
35 #include "string-util.h"
37 #include "xattr-util.h"
50 static int vacuum_compare(const void *_a
, const void *_b
) {
51 const struct vacuum_info
*a
, *b
;
56 if (a
->have_seqnum
&& b
->have_seqnum
&&
57 sd_id128_equal(a
->seqnum_id
, b
->seqnum_id
)) {
58 if (a
->seqnum
< b
->seqnum
)
60 else if (a
->seqnum
> b
->seqnum
)
66 if (a
->realtime
< b
->realtime
)
68 else if (a
->realtime
> b
->realtime
)
70 else if (a
->have_seqnum
&& b
->have_seqnum
)
71 return memcmp(&a
->seqnum_id
, &b
->seqnum_id
, 16);
73 return strcmp(a
->filename
, b
->filename
);
76 static void patch_realtime(
79 const struct stat
*st
,
80 unsigned long long *realtime
) {
84 /* The timestamp was determined by the file name, but let's
85 * see if the file might actually be older than the file name
93 x
= timespec_load(&st
->st_ctim
);
94 if (x
> 0 && x
!= USEC_INFINITY
&& x
< *realtime
)
97 x
= timespec_load(&st
->st_atim
);
98 if (x
> 0 && x
!= USEC_INFINITY
&& x
< *realtime
)
101 x
= timespec_load(&st
->st_mtim
);
102 if (x
> 0 && x
!= USEC_INFINITY
&& x
< *realtime
)
105 /* Let's read the original creation time, if possible. Ideally
106 * we'd just query the creation time the FS might provide, but
107 * unfortunately there's currently no sane API to query
108 * it. Hence let's implement this manually... */
110 if (fd_getcrtime_at(fd
, fn
, &crtime
, 0) >= 0) {
111 if (crtime
< *realtime
)
116 static int journal_file_empty(int dir_fd
, const char *name
) {
117 _cleanup_close_
int fd
;
122 fd
= openat(dir_fd
, name
, O_RDONLY
|O_CLOEXEC
|O_NOFOLLOW
|O_NONBLOCK
|O_NOATIME
);
124 /* Maybe failed due to O_NOATIME and lack of privileges? */
125 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 uint64_t n_max_files
,
151 usec_t max_retention_usec
,
155 _cleanup_closedir_
DIR *d
= NULL
;
156 struct vacuum_info
*list
= NULL
;
157 unsigned n_list
= 0, i
, n_active_files
= 0;
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
];
167 if (max_use
<= 0 && max_retention_usec
<= 0 && n_max_files
<= 0)
170 if (max_retention_usec
> 0) {
171 retention_limit
= now(CLOCK_REALTIME
);
172 if (retention_limit
> max_retention_usec
)
173 retention_limit
-= max_retention_usec
;
175 max_retention_usec
= retention_limit
= 0;
178 d
= opendir(directory
);
182 FOREACH_DIRENT_ALL(de
, d
, r
= -errno
; goto finish
) {
184 unsigned long long seqnum
= 0, realtime
;
185 _cleanup_free_
char *p
= NULL
;
186 sd_id128_t seqnum_id
;
192 if (fstatat(dirfd(d
), de
->d_name
, &st
, AT_SYMLINK_NOFOLLOW
) < 0) {
193 log_debug_errno(errno
, "Failed to stat file %s while vacuuming, ignoring: %m", de
->d_name
);
197 if (!S_ISREG(st
.st_mode
))
200 q
= strlen(de
->d_name
);
202 if (endswith(de
->d_name
, ".journal")) {
204 /* Vacuum archived files. Active files are
207 if (q
< 1 + 32 + 1 + 16 + 1 + 16 + 8) {
212 if (de
->d_name
[q
-8-16-1] != '-' ||
213 de
->d_name
[q
-8-16-1-16-1] != '-' ||
214 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) {
248 if (de
->d_name
[q
-1-8-16-1] != '-' ||
249 de
->d_name
[q
-1-8-16-1-16-1] != '@') {
254 p
= strdup(de
->d_name
);
260 if (sscanf(de
->d_name
+ q
-1-8-16-1-16, "%16llx-%16llx.journal~", &realtime
, &tmp
) != 2) {
267 /* We do not vacuum unknown files! */
268 log_debug("Not vacuuming unknown file %s.", de
->d_name
);
272 size
= 512UL * (uint64_t) st
.st_blocks
;
274 r
= journal_file_empty(dirfd(d
), p
);
276 log_debug_errno(r
, "Failed check if %s is empty, ignoring: %m", p
);
280 /* Always vacuum empty non-online files. */
282 r
= unlinkat_deallocate(dirfd(d
), p
, 0);
285 log_full(verbose
? LOG_INFO
: LOG_DEBUG
,
286 "Deleted empty archived journal %s/%s (%s).", directory
, p
, format_bytes(sbytes
, sizeof(sbytes
), size
));
289 } else if (r
!= -ENOENT
)
290 log_warning_errno(r
, "Failed to delete empty archived journal %s/%s: %m", directory
, p
);
295 patch_realtime(dirfd(d
), p
, &st
, &realtime
);
297 if (!GREEDY_REALLOC(list
, n_allocated
, n_list
+ 1)) {
302 list
[n_list
].filename
= p
;
303 list
[n_list
].usage
= size
;
304 list
[n_list
].seqnum
= seqnum
;
305 list
[n_list
].realtime
= realtime
;
306 list
[n_list
].seqnum_id
= seqnum_id
;
307 list
[n_list
].have_seqnum
= have_seqnum
;
314 qsort_safe(list
, n_list
, sizeof(struct vacuum_info
), vacuum_compare
);
316 for (i
= 0; i
< n_list
; i
++) {
319 left
= n_active_files
+ n_list
- i
;
321 if ((max_retention_usec
<= 0 || list
[i
].realtime
>= retention_limit
) &&
322 (max_use
<= 0 || sum
<= max_use
) &&
323 (n_max_files
<= 0 || left
<= n_max_files
))
326 r
= unlinkat_deallocate(dirfd(d
), list
[i
].filename
, 0);
328 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
));
329 freed
+= list
[i
].usage
;
331 if (list
[i
].usage
< sum
)
332 sum
-= list
[i
].usage
;
336 } else if (r
!= -ENOENT
)
337 log_warning_errno(r
, "Failed to delete archived journal %s/%s: %m", directory
, list
[i
].filename
);
340 if (oldest_usec
&& i
< n_list
&& (*oldest_usec
== 0 || list
[i
].realtime
< *oldest_usec
))
341 *oldest_usec
= list
[i
].realtime
;
346 for (i
= 0; i
< n_list
; i
++)
347 free(list
[i
].filename
);
350 log_full(verbose
? LOG_INFO
: LOG_DEBUG
, "Vacuuming done, freed %s of archived journals from %s.", format_bytes(sbytes
, sizeof(sbytes
), freed
), directory
);