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 "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"
48 static int vacuum_compare(const void *_a
, const void *_b
) {
49 const struct vacuum_info
*a
, *b
;
54 if (a
->have_seqnum
&& b
->have_seqnum
&&
55 sd_id128_equal(a
->seqnum_id
, b
->seqnum_id
)) {
56 if (a
->seqnum
< b
->seqnum
)
58 else if (a
->seqnum
> b
->seqnum
)
64 if (a
->realtime
< b
->realtime
)
66 else if (a
->realtime
> b
->realtime
)
68 else if (a
->have_seqnum
&& b
->have_seqnum
)
69 return memcmp(&a
->seqnum_id
, &b
->seqnum_id
, 16);
71 return strcmp(a
->filename
, b
->filename
);
74 static void patch_realtime(
77 const struct stat
*st
,
78 unsigned long long *realtime
) {
82 /* The timestamp was determined by the file name, but let's
83 * see if the file might actually be older than the file name
91 x
= timespec_load(&st
->st_ctim
);
92 if (x
> 0 && x
!= USEC_INFINITY
&& x
< *realtime
)
95 x
= timespec_load(&st
->st_atim
);
96 if (x
> 0 && x
!= USEC_INFINITY
&& x
< *realtime
)
99 x
= timespec_load(&st
->st_mtim
);
100 if (x
> 0 && x
!= USEC_INFINITY
&& x
< *realtime
)
103 /* Let's read the original creation time, if possible. Ideally
104 * we'd just query the creation time the FS might provide, but
105 * unfortunately there's currently no sane API to query
106 * it. Hence let's implement this manually... */
108 if (fd_getcrtime_at(fd
, fn
, &crtime
, 0) >= 0) {
109 if (crtime
< *realtime
)
114 static int journal_file_empty(int dir_fd
, const char *name
) {
115 _cleanup_close_
int fd
;
120 fd
= openat(dir_fd
, name
, O_RDONLY
|O_CLOEXEC
|O_NOFOLLOW
|O_NONBLOCK
|O_NOATIME
);
122 /* Maybe failed due to O_NOATIME and lack of privileges? */
123 fd
= openat(dir_fd
, name
, O_RDONLY
|O_CLOEXEC
|O_NOFOLLOW
|O_NONBLOCK
);
128 if (fstat(fd
, &st
) < 0)
131 /* If an offline file doesn't even have a header we consider it empty */
132 if (st
.st_size
< (off_t
) sizeof(Header
))
135 /* If the number of entries is empty, we consider it empty, too */
136 n
= pread(fd
, &n_entries
, sizeof(n_entries
), offsetof(Header
, n_entries
));
139 if (n
!= sizeof(n_entries
))
142 return le64toh(n_entries
) <= 0;
145 int journal_directory_vacuum(
146 const char *directory
,
148 uint64_t n_max_files
,
149 usec_t max_retention_usec
,
153 _cleanup_closedir_
DIR *d
= NULL
;
154 struct vacuum_info
*list
= NULL
;
155 unsigned n_list
= 0, i
, n_active_files
= 0;
156 size_t n_allocated
= 0;
157 uint64_t sum
= 0, freed
= 0;
158 usec_t retention_limit
= 0;
159 char sbytes
[FORMAT_BYTES_MAX
];
165 if (max_use
<= 0 && max_retention_usec
<= 0 && n_max_files
<= 0)
168 if (max_retention_usec
> 0) {
169 retention_limit
= now(CLOCK_REALTIME
);
170 if (retention_limit
> max_retention_usec
)
171 retention_limit
-= max_retention_usec
;
173 max_retention_usec
= retention_limit
= 0;
176 d
= opendir(directory
);
180 FOREACH_DIRENT_ALL(de
, d
, r
= -errno
; goto finish
) {
182 unsigned long long seqnum
= 0, realtime
;
183 _cleanup_free_
char *p
= NULL
;
184 sd_id128_t seqnum_id
;
190 if (fstatat(dirfd(d
), de
->d_name
, &st
, AT_SYMLINK_NOFOLLOW
) < 0) {
191 log_debug_errno(errno
, "Failed to stat file %s while vacuuming, ignoring: %m", de
->d_name
);
195 if (!S_ISREG(st
.st_mode
))
198 q
= strlen(de
->d_name
);
200 if (endswith(de
->d_name
, ".journal")) {
202 /* Vacuum archived files. Active files are
205 if (q
< 1 + 32 + 1 + 16 + 1 + 16 + 8) {
210 if (de
->d_name
[q
-8-16-1] != '-' ||
211 de
->d_name
[q
-8-16-1-16-1] != '-' ||
212 de
->d_name
[q
-8-16-1-16-1-32-1] != '@') {
217 p
= strdup(de
->d_name
);
223 de
->d_name
[q
-8-16-1-16-1] = 0;
224 if (sd_id128_from_string(de
->d_name
+ q
-8-16-1-16-1-32, &seqnum_id
) < 0) {
229 if (sscanf(de
->d_name
+ q
-8-16-1-16, "%16llx-%16llx.journal", &seqnum
, &realtime
) != 2) {
236 } else if (endswith(de
->d_name
, ".journal~")) {
237 unsigned long long tmp
;
239 /* Vacuum corrupted files */
241 if (q
< 1 + 16 + 1 + 16 + 8 + 1) {
246 if (de
->d_name
[q
-1-8-16-1] != '-' ||
247 de
->d_name
[q
-1-8-16-1-16-1] != '@') {
252 p
= strdup(de
->d_name
);
258 if (sscanf(de
->d_name
+ q
-1-8-16-1-16, "%16llx-%16llx.journal~", &realtime
, &tmp
) != 2) {
265 /* We do not vacuum unknown files! */
266 log_debug("Not vacuuming unknown file %s.", de
->d_name
);
270 size
= 512UL * (uint64_t) st
.st_blocks
;
272 r
= journal_file_empty(dirfd(d
), p
);
274 log_debug_errno(r
, "Failed check if %s is empty, ignoring: %m", p
);
278 /* Always vacuum empty non-online files. */
280 if (unlinkat(dirfd(d
), p
, 0) >= 0) {
282 log_full(verbose
? LOG_INFO
: LOG_DEBUG
,
283 "Deleted empty archived journal %s/%s (%s).", directory
, p
, format_bytes(sbytes
, sizeof(sbytes
), size
));
286 } else if (errno
!= ENOENT
)
287 log_warning_errno(errno
, "Failed to delete empty archived journal %s/%s: %m", directory
, p
);
292 patch_realtime(dirfd(d
), p
, &st
, &realtime
);
294 if (!GREEDY_REALLOC(list
, n_allocated
, n_list
+ 1)) {
299 list
[n_list
].filename
= p
;
300 list
[n_list
].usage
= size
;
301 list
[n_list
].seqnum
= seqnum
;
302 list
[n_list
].realtime
= realtime
;
303 list
[n_list
].seqnum_id
= seqnum_id
;
304 list
[n_list
].have_seqnum
= have_seqnum
;
311 qsort_safe(list
, n_list
, sizeof(struct vacuum_info
), vacuum_compare
);
313 for (i
= 0; i
< n_list
; i
++) {
316 left
= n_active_files
+ n_list
- i
;
318 if ((max_retention_usec
<= 0 || list
[i
].realtime
>= retention_limit
) &&
319 (max_use
<= 0 || sum
<= max_use
) &&
320 (n_max_files
<= 0 || left
<= n_max_files
))
323 if (unlinkat(dirfd(d
), list
[i
].filename
, 0) >= 0) {
324 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
));
325 freed
+= list
[i
].usage
;
327 if (list
[i
].usage
< sum
)
328 sum
-= list
[i
].usage
;
332 } else if (errno
!= ENOENT
)
333 log_warning_errno(errno
, "Failed to delete archived journal %s/%s: %m", directory
, list
[i
].filename
);
336 if (oldest_usec
&& i
< n_list
&& (*oldest_usec
== 0 || list
[i
].realtime
< *oldest_usec
))
337 *oldest_usec
= list
[i
].realtime
;
342 for (i
= 0; i
< n_list
; i
++)
343 free(list
[i
].filename
);
346 log_full(verbose
? LOG_INFO
: LOG_DEBUG
, "Vacuuming done, freed %s of archived journals on disk.", format_bytes(sbytes
, sizeof(sbytes
), freed
));