2 This file is part of systemd.
4 Copyright 2011 Lennart Poettering
6 systemd is free software; you can redistribute it and/or modify it
7 under the terms of the GNU Lesser General Public License as published by
8 the Free Software Foundation; either version 2.1 of the License, or
9 (at your option) any later version.
11 systemd is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 Lesser General Public License for more details.
16 You should have received a copy of the GNU Lesser General Public License
17 along with systemd; If not, see <http://www.gnu.org/licenses/>.
26 #include "alloc-util.h"
27 #include "dirent-util.h"
29 #include "journal-def.h"
30 #include "journal-file.h"
31 #include "journal-vacuum.h"
32 #include "parse-util.h"
33 #include "string-util.h"
35 #include "xattr-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
));