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/>.
26 #include "journal-def.h"
27 #include "journal-file.h"
28 #include "journal-vacuum.h"
43 static int vacuum_compare(const void *_a
, const void *_b
) {
44 const struct vacuum_info
*a
, *b
;
49 if (a
->have_seqnum
&& b
->have_seqnum
&&
50 sd_id128_equal(a
->seqnum_id
, b
->seqnum_id
)) {
51 if (a
->seqnum
< b
->seqnum
)
53 else if (a
->seqnum
> b
->seqnum
)
59 if (a
->realtime
< b
->realtime
)
61 else if (a
->realtime
> b
->realtime
)
63 else if (a
->have_seqnum
&& b
->have_seqnum
)
64 return memcmp(&a
->seqnum_id
, &b
->seqnum_id
, 16);
66 return strcmp(a
->filename
, b
->filename
);
69 static void patch_realtime(
72 const struct stat
*st
,
73 unsigned long long *realtime
) {
77 /* The timestamp was determined by the file name, but let's
78 * see if the file might actually be older than the file name
86 x
= timespec_load(&st
->st_ctim
);
87 if (x
> 0 && x
!= USEC_INFINITY
&& x
< *realtime
)
90 x
= timespec_load(&st
->st_atim
);
91 if (x
> 0 && x
!= USEC_INFINITY
&& x
< *realtime
)
94 x
= timespec_load(&st
->st_mtim
);
95 if (x
> 0 && x
!= USEC_INFINITY
&& x
< *realtime
)
98 /* Let's read the original creation time, if possible. Ideally
99 * we'd just query the creation time the FS might provide, but
100 * unfortunately there's currently no sane API to query
101 * it. Hence let's implement this manually... */
103 if (fd_getcrtime_at(fd
, fn
, &crtime
, 0) >= 0) {
104 if (crtime
< *realtime
)
109 static int journal_file_empty(int dir_fd
, const char *name
) {
110 _cleanup_close_
int fd
;
115 fd
= openat(dir_fd
, name
, O_RDONLY
|O_CLOEXEC
|O_NOFOLLOW
|O_NONBLOCK
);
119 if (fstat(fd
, &st
) < 0)
122 /* If an offline file doesn't even have a header we consider it empty */
123 if (st
.st_size
< (off_t
) sizeof(Header
))
126 /* If the number of entries is empty, we consider it empty, too */
127 n
= pread(fd
, &n_entries
, sizeof(n_entries
), offsetof(Header
, n_entries
));
130 if (n
!= sizeof(n_entries
))
133 return le64toh(n_entries
) <= 0;
136 int journal_directory_vacuum(
137 const char *directory
,
139 usec_t max_retention_usec
,
143 _cleanup_closedir_
DIR *d
= NULL
;
145 struct vacuum_info
*list
= NULL
;
146 unsigned n_list
= 0, i
;
147 size_t n_allocated
= 0;
148 uint64_t sum
= 0, freed
= 0;
149 usec_t retention_limit
= 0;
150 char sbytes
[FORMAT_BYTES_MAX
];
154 if (max_use
<= 0 && max_retention_usec
<= 0)
157 if (max_retention_usec
> 0) {
158 retention_limit
= now(CLOCK_REALTIME
);
159 if (retention_limit
> max_retention_usec
)
160 retention_limit
-= max_retention_usec
;
162 max_retention_usec
= retention_limit
= 0;
165 d
= opendir(directory
);
174 unsigned long long seqnum
= 0, realtime
;
175 sd_id128_t seqnum_id
;
180 if (!de
&& errno
!= 0) {
188 if (fstatat(dirfd(d
), de
->d_name
, &st
, AT_SYMLINK_NOFOLLOW
) < 0)
191 if (!S_ISREG(st
.st_mode
))
194 q
= strlen(de
->d_name
);
196 if (endswith(de
->d_name
, ".journal")) {
198 /* Vacuum archived files */
200 if (q
< 1 + 32 + 1 + 16 + 1 + 16 + 8)
203 if (de
->d_name
[q
-8-16-1] != '-' ||
204 de
->d_name
[q
-8-16-1-16-1] != '-' ||
205 de
->d_name
[q
-8-16-1-16-1-32-1] != '@')
208 p
= strdup(de
->d_name
);
214 de
->d_name
[q
-8-16-1-16-1] = 0;
215 if (sd_id128_from_string(de
->d_name
+ q
-8-16-1-16-1-32, &seqnum_id
) < 0) {
220 if (sscanf(de
->d_name
+ q
-8-16-1-16, "%16llx-%16llx.journal", &seqnum
, &realtime
) != 2) {
227 } else if (endswith(de
->d_name
, ".journal~")) {
228 unsigned long long tmp
;
230 /* Vacuum corrupted files */
232 if (q
< 1 + 16 + 1 + 16 + 8 + 1)
235 if (de
->d_name
[q
-1-8-16-1] != '-' ||
236 de
->d_name
[q
-1-8-16-1-16-1] != '@')
239 p
= strdup(de
->d_name
);
245 if (sscanf(de
->d_name
+ q
-1-8-16-1-16, "%16llx-%16llx.journal~", &realtime
, &tmp
) != 2) {
252 /* We do not vacuum active files or unknown files! */
255 if (journal_file_empty(dirfd(d
), p
)) {
256 /* Always vacuum empty non-online files. */
258 uint64_t size
= 512UL * (uint64_t) st
.st_blocks
;
260 if (unlinkat(dirfd(d
), p
, 0) >= 0) {
261 log_full(verbose
? LOG_INFO
: LOG_DEBUG
, "Deleted empty archived journal %s/%s (%s).", directory
, p
, format_bytes(sbytes
, sizeof(sbytes
), size
));
263 } else if (errno
!= ENOENT
)
264 log_warning_errno(errno
, "Failed to delete empty archived journal %s/%s: %m", directory
, p
);
270 patch_realtime(directory
, p
, &st
, &realtime
);
272 if (!GREEDY_REALLOC(list
, n_allocated
, n_list
+ 1)) {
278 list
[n_list
].filename
= p
;
279 list
[n_list
].usage
= 512UL * (uint64_t) st
.st_blocks
;
280 list
[n_list
].seqnum
= seqnum
;
281 list
[n_list
].realtime
= realtime
;
282 list
[n_list
].seqnum_id
= seqnum_id
;
283 list
[n_list
].have_seqnum
= have_seqnum
;
285 sum
+= list
[n_list
].usage
;
290 qsort_safe(list
, n_list
, sizeof(struct vacuum_info
), vacuum_compare
);
292 for (i
= 0; i
< n_list
; i
++) {
293 if ((max_retention_usec
<= 0 || list
[i
].realtime
>= retention_limit
) &&
294 (max_use
<= 0 || sum
<= max_use
))
297 if (unlinkat(dirfd(d
), list
[i
].filename
, 0) >= 0) {
298 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
));
299 freed
+= list
[i
].usage
;
301 if (list
[i
].usage
< sum
)
302 sum
-= list
[i
].usage
;
306 } else if (errno
!= ENOENT
)
307 log_warning_errno(errno
, "Failed to delete archived journal %s/%s: %m", 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
;
314 for (i
= 0; i
< n_list
; i
++)
315 free(list
[i
].filename
);
318 log_full(verbose
? LOG_INFO
: LOG_DEBUG
, "Vacuuming done, freed %s of archived journals on disk.", format_bytes(sbytes
, sizeof(sbytes
), freed
));