1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
9 #include "alloc-util.h"
10 #include "dirent-util.h"
12 #include "format-util.h"
14 #include "journal-def.h"
15 #include "journal-file.h"
16 #include "journal-internal.h"
17 #include "journal-vacuum.h"
18 #include "sort-util.h"
19 #include "string-util.h"
20 #include "time-util.h"
21 #include "xattr-util.h"
34 static int vacuum_compare(const struct vacuum_info
*a
, const struct vacuum_info
*b
) {
37 if (a
->have_seqnum
&& b
->have_seqnum
&&
38 sd_id128_equal(a
->seqnum_id
, b
->seqnum_id
))
39 return CMP(a
->seqnum
, b
->seqnum
);
41 r
= CMP(a
->realtime
, b
->realtime
);
45 if (a
->have_seqnum
&& b
->have_seqnum
)
46 return memcmp(&a
->seqnum_id
, &b
->seqnum_id
, 16);
48 return strcmp(a
->filename
, b
->filename
);
51 static void patch_realtime(
54 const struct stat
*st
,
55 unsigned long long *realtime
) {
59 /* The timestamp was determined by the file name, but let's see if the file might actually be older
60 * than the file name suggested... */
67 x
= timespec_load(&st
->st_ctim
);
68 if (timestamp_is_set(x
) && x
< *realtime
)
71 x
= timespec_load(&st
->st_atim
);
72 if (timestamp_is_set(x
) && x
< *realtime
)
75 x
= timespec_load(&st
->st_mtim
);
76 if (timestamp_is_set(x
) && x
< *realtime
)
79 /* Let's read the original creation time, if possible. Ideally we'd just query the creation time the
80 * FS might provide, but unfortunately there's currently no sane API to query it. Hence let's
81 * implement this manually... */
83 if (fd_getcrtime_at(fd
, fn
, AT_SYMLINK_FOLLOW
, &x
) >= 0 && x
< *realtime
)
87 static int journal_file_empty(int dir_fd
, const char *name
) {
88 _cleanup_close_
int fd
= -EBADF
;
93 fd
= openat(dir_fd
, name
, O_RDONLY
|O_CLOEXEC
|O_NOFOLLOW
|O_NONBLOCK
|O_NOATIME
);
95 /* Maybe failed due to O_NOATIME and lack of privileges? */
96 fd
= openat(dir_fd
, name
, O_RDONLY
|O_CLOEXEC
|O_NOFOLLOW
|O_NONBLOCK
);
101 if (fstat(fd
, &st
) < 0)
104 /* If an offline file doesn't even have a header we consider it empty */
105 if (st
.st_size
< (off_t
) sizeof(Header
))
108 /* If the number of entries is empty, we consider it empty, too */
109 n
= pread(fd
, &n_entries
, sizeof(n_entries
), offsetof(Header
, n_entries
));
112 if (n
!= sizeof(n_entries
))
115 return le64toh(n_entries
) <= 0;
118 int journal_directory_vacuum(
119 const char *directory
,
121 uint64_t n_max_files
,
122 usec_t max_retention_usec
,
126 uint64_t sum
= 0, freed
= 0, n_active_files
= 0;
127 size_t n_list
= 0, i
;
128 _cleanup_closedir_
DIR *d
= NULL
;
129 struct vacuum_info
*list
= NULL
;
130 usec_t retention_limit
= 0;
135 if (max_use
<= 0 && max_retention_usec
<= 0 && n_max_files
<= 0)
138 if (max_retention_usec
> 0)
139 retention_limit
= usec_sub_unsigned(now(CLOCK_REALTIME
), max_retention_usec
);
141 d
= opendir(directory
);
145 FOREACH_DIRENT_ALL(de
, d
, r
= -errno
; goto finish
) {
146 unsigned long long seqnum
= 0, realtime
;
147 _cleanup_free_
char *p
= NULL
;
148 sd_id128_t seqnum_id
;
154 if (fstatat(dirfd(d
), de
->d_name
, &st
, AT_SYMLINK_NOFOLLOW
) < 0) {
155 log_debug_errno(errno
, "Failed to stat file %s while vacuuming, ignoring: %m", de
->d_name
);
159 if (!S_ISREG(st
.st_mode
))
162 q
= strlen(de
->d_name
);
164 if (endswith(de
->d_name
, ".journal")) {
166 /* Vacuum archived files. Active files are
169 if (q
< 1 + 32 + 1 + 16 + 1 + 16 + 8) {
174 if (de
->d_name
[q
-8-16-1] != '-' ||
175 de
->d_name
[q
-8-16-1-16-1] != '-' ||
176 de
->d_name
[q
-8-16-1-16-1-32-1] != '@') {
181 p
= strdup(de
->d_name
);
187 de
->d_name
[q
-8-16-1-16-1] = 0;
188 if (sd_id128_from_string(de
->d_name
+ q
-8-16-1-16-1-32, &seqnum_id
) < 0) {
193 if (sscanf(de
->d_name
+ q
-8-16-1-16, "%16llx-%16llx.journal", &seqnum
, &realtime
) != 2) {
200 } else if (endswith(de
->d_name
, ".journal~")) {
201 unsigned long long tmp
;
203 /* seqnum_id won't be initialised before use below, so set to 0 */
204 seqnum_id
= SD_ID128_NULL
;
206 /* Vacuum corrupted files */
208 if (q
< 1 + 16 + 1 + 16 + 8 + 1) {
213 if (de
->d_name
[q
-1-8-16-1] != '-' ||
214 de
->d_name
[q
-1-8-16-1-16-1] != '@') {
219 p
= strdup(de
->d_name
);
225 if (sscanf(de
->d_name
+ q
-1-8-16-1-16, "%16llx-%16llx.journal~", &realtime
, &tmp
) != 2) {
232 /* We do not vacuum unknown files! */
233 log_debug("Not vacuuming unknown file %s.", de
->d_name
);
237 size
= 512UL * (uint64_t) st
.st_blocks
;
239 r
= journal_file_empty(dirfd(d
), p
);
241 log_debug_errno(r
, "Failed check if %s is empty, ignoring: %m", p
);
245 /* Always vacuum empty non-online files. */
247 r
= unlinkat_deallocate(dirfd(d
), p
, 0);
250 log_full(verbose
? LOG_INFO
: LOG_DEBUG
,
251 "Deleted empty archived journal %s/%s (%s).", directory
, p
, FORMAT_BYTES(size
));
254 } else if (r
!= -ENOENT
)
255 log_ratelimit_warning_errno(r
, JOURNAL_LOG_RATELIMIT
,
256 "Failed to delete empty archived journal %s/%s: %m",
262 patch_realtime(dirfd(d
), p
, &st
, &realtime
);
264 if (!GREEDY_REALLOC(list
, n_list
+ 1)) {
269 list
[n_list
++] = (struct vacuum_info
) {
270 .filename
= TAKE_PTR(p
),
273 .realtime
= realtime
,
274 .seqnum_id
= seqnum_id
,
275 .have_seqnum
= have_seqnum
,
281 typesafe_qsort(list
, n_list
, vacuum_compare
);
283 for (i
= 0; i
< n_list
; i
++) {
286 left
= n_active_files
+ n_list
- i
;
288 if ((max_retention_usec
<= 0 || list
[i
].realtime
>= retention_limit
) &&
289 (max_use
<= 0 || sum
<= max_use
) &&
290 (n_max_files
<= 0 || left
<= n_max_files
))
293 r
= unlinkat_deallocate(dirfd(d
), list
[i
].filename
, 0);
295 log_full(verbose
? LOG_INFO
: LOG_DEBUG
, "Deleted archived journal %s/%s (%s).",
296 directory
, list
[i
].filename
, FORMAT_BYTES(list
[i
].usage
));
297 freed
+= list
[i
].usage
;
299 if (list
[i
].usage
< sum
)
300 sum
-= list
[i
].usage
;
304 } else if (r
!= -ENOENT
)
305 log_ratelimit_warning_errno(r
, JOURNAL_LOG_RATELIMIT
,
306 "Failed to delete archived journal %s/%s: %m",
307 directory
, list
[i
].filename
);
310 if (oldest_usec
&& i
< n_list
&& (*oldest_usec
== 0 || list
[i
].realtime
< *oldest_usec
))
311 *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 from %s.",
321 FORMAT_BYTES(freed
), directory
);