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/>.
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"
47 static int vacuum_compare(const void *_a
, const void *_b
) {
48 const struct vacuum_info
*a
, *b
;
53 if (a
->have_seqnum
&& b
->have_seqnum
&&
54 sd_id128_equal(a
->seqnum_id
, b
->seqnum_id
)) {
55 if (a
->seqnum
< b
->seqnum
)
57 else if (a
->seqnum
> b
->seqnum
)
63 if (a
->realtime
< b
->realtime
)
65 else if (a
->realtime
> b
->realtime
)
67 else if (a
->have_seqnum
&& b
->have_seqnum
)
68 return memcmp(&a
->seqnum_id
, &b
->seqnum_id
, 16);
70 return strcmp(a
->filename
, b
->filename
);
73 static void patch_realtime(
76 const struct stat
*st
,
77 unsigned long long *realtime
) {
81 /* The timestamp was determined by the file name, but let's
82 * see if the file might actually be older than the file name
90 x
= timespec_load(&st
->st_ctim
);
91 if (x
> 0 && x
!= USEC_INFINITY
&& x
< *realtime
)
94 x
= timespec_load(&st
->st_atim
);
95 if (x
> 0 && x
!= USEC_INFINITY
&& x
< *realtime
)
98 x
= timespec_load(&st
->st_mtim
);
99 if (x
> 0 && x
!= USEC_INFINITY
&& x
< *realtime
)
102 /* Let's read the original creation time, if possible. Ideally
103 * we'd just query the creation time the FS might provide, but
104 * unfortunately there's currently no sane API to query
105 * it. Hence let's implement this manually... */
107 if (fd_getcrtime_at(fd
, fn
, &crtime
, 0) >= 0) {
108 if (crtime
< *realtime
)
113 static int journal_file_empty(int dir_fd
, const char *name
) {
114 _cleanup_close_
int fd
;
119 fd
= openat(dir_fd
, name
, O_RDONLY
|O_CLOEXEC
|O_NOFOLLOW
|O_NONBLOCK
|O_NOATIME
);
121 /* Maybe failed due to O_NOATIME and lack of privileges? */
122 fd
= openat(dir_fd
, name
, O_RDONLY
|O_CLOEXEC
|O_NOFOLLOW
|O_NONBLOCK
);
127 if (fstat(fd
, &st
) < 0)
130 /* If an offline file doesn't even have a header we consider it empty */
131 if (st
.st_size
< (off_t
) sizeof(Header
))
134 /* If the number of entries is empty, we consider it empty, too */
135 n
= pread(fd
, &n_entries
, sizeof(n_entries
), offsetof(Header
, n_entries
));
138 if (n
!= sizeof(n_entries
))
141 return le64toh(n_entries
) <= 0;
144 int journal_directory_vacuum(
145 const char *directory
,
147 uint64_t n_max_files
,
148 usec_t max_retention_usec
,
152 _cleanup_closedir_
DIR *d
= NULL
;
153 struct vacuum_info
*list
= NULL
;
154 unsigned n_list
= 0, i
, n_active_files
= 0;
155 size_t n_allocated
= 0;
156 uint64_t sum
= 0, freed
= 0;
157 usec_t retention_limit
= 0;
158 char sbytes
[FORMAT_BYTES_MAX
];
164 if (max_use
<= 0 && max_retention_usec
<= 0 && n_max_files
<= 0)
167 if (max_retention_usec
> 0) {
168 retention_limit
= now(CLOCK_REALTIME
);
169 if (retention_limit
> max_retention_usec
)
170 retention_limit
-= max_retention_usec
;
172 max_retention_usec
= retention_limit
= 0;
175 d
= opendir(directory
);
179 FOREACH_DIRENT_ALL(de
, d
, r
= -errno
; goto finish
) {
181 unsigned long long seqnum
= 0, realtime
;
182 _cleanup_free_
char *p
= NULL
;
183 sd_id128_t seqnum_id
;
189 if (fstatat(dirfd(d
), de
->d_name
, &st
, AT_SYMLINK_NOFOLLOW
) < 0) {
190 log_debug_errno(errno
, "Failed to stat file %s while vacuuming, ignoring: %m", de
->d_name
);
194 if (!S_ISREG(st
.st_mode
))
197 q
= strlen(de
->d_name
);
199 if (endswith(de
->d_name
, ".journal")) {
201 /* Vacuum archived files. Active files are
204 if (q
< 1 + 32 + 1 + 16 + 1 + 16 + 8) {
209 if (de
->d_name
[q
-8-16-1] != '-' ||
210 de
->d_name
[q
-8-16-1-16-1] != '-' ||
211 de
->d_name
[q
-8-16-1-16-1-32-1] != '@') {
216 p
= strdup(de
->d_name
);
222 de
->d_name
[q
-8-16-1-16-1] = 0;
223 if (sd_id128_from_string(de
->d_name
+ q
-8-16-1-16-1-32, &seqnum_id
) < 0) {
228 if (sscanf(de
->d_name
+ q
-8-16-1-16, "%16llx-%16llx.journal", &seqnum
, &realtime
) != 2) {
235 } else if (endswith(de
->d_name
, ".journal~")) {
236 unsigned long long tmp
;
238 /* Vacuum corrupted files */
240 if (q
< 1 + 16 + 1 + 16 + 8 + 1) {
245 if (de
->d_name
[q
-1-8-16-1] != '-' ||
246 de
->d_name
[q
-1-8-16-1-16-1] != '@') {
251 p
= strdup(de
->d_name
);
257 if (sscanf(de
->d_name
+ q
-1-8-16-1-16, "%16llx-%16llx.journal~", &realtime
, &tmp
) != 2) {
264 /* We do not vacuum unknown files! */
265 log_debug("Not vacuuming unknown file %s.", de
->d_name
);
269 size
= 512UL * (uint64_t) st
.st_blocks
;
271 r
= journal_file_empty(dirfd(d
), p
);
273 log_debug_errno(r
, "Failed check if %s is empty, ignoring: %m", p
);
277 /* Always vacuum empty non-online files. */
279 if (unlinkat(dirfd(d
), p
, 0) >= 0) {
281 log_full(verbose
? LOG_INFO
: LOG_DEBUG
,
282 "Deleted empty archived journal %s/%s (%s).", directory
, p
, format_bytes(sbytes
, sizeof(sbytes
), size
));
285 } else if (errno
!= ENOENT
)
286 log_warning_errno(errno
, "Failed to delete empty archived journal %s/%s: %m", directory
, p
);
291 patch_realtime(dirfd(d
), p
, &st
, &realtime
);
293 if (!GREEDY_REALLOC(list
, n_allocated
, n_list
+ 1)) {
298 list
[n_list
].filename
= p
;
299 list
[n_list
].usage
= size
;
300 list
[n_list
].seqnum
= seqnum
;
301 list
[n_list
].realtime
= realtime
;
302 list
[n_list
].seqnum_id
= seqnum_id
;
303 list
[n_list
].have_seqnum
= have_seqnum
;
310 qsort_safe(list
, n_list
, sizeof(struct vacuum_info
), vacuum_compare
);
312 for (i
= 0; i
< n_list
; i
++) {
315 left
= n_active_files
+ n_list
- i
;
317 if ((max_retention_usec
<= 0 || list
[i
].realtime
>= retention_limit
) &&
318 (max_use
<= 0 || sum
<= max_use
) &&
319 (n_max_files
<= 0 || left
<= n_max_files
))
322 if (unlinkat(dirfd(d
), list
[i
].filename
, 0) >= 0) {
323 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
));
324 freed
+= list
[i
].usage
;
326 if (list
[i
].usage
< sum
)
327 sum
-= list
[i
].usage
;
331 } else if (errno
!= ENOENT
)
332 log_warning_errno(errno
, "Failed to delete archived journal %s/%s: %m", directory
, list
[i
].filename
);
335 if (oldest_usec
&& i
< n_list
&& (*oldest_usec
== 0 || list
[i
].realtime
< *oldest_usec
))
336 *oldest_usec
= list
[i
].realtime
;
341 for (i
= 0; i
< n_list
; i
++)
342 free(list
[i
].filename
);
345 log_full(verbose
? LOG_INFO
: LOG_DEBUG
, "Vacuuming done, freed %s of archived journals on disk.", format_bytes(sbytes
, sizeof(sbytes
), freed
));