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
) {
75 _cleanup_free_
const char *path
= NULL
;
78 /* The timestamp was determined by the file name, but let's
79 * see if the file might actually be older than the file name
87 x
= timespec_load(&st
->st_ctim
);
88 if (x
> 0 && x
!= USEC_INFINITY
&& x
< *realtime
)
91 x
= timespec_load(&st
->st_atim
);
92 if (x
> 0 && x
!= USEC_INFINITY
&& x
< *realtime
)
95 x
= timespec_load(&st
->st_mtim
);
96 if (x
> 0 && x
!= USEC_INFINITY
&& x
< *realtime
)
99 /* Let's read the original creation time, if possible. Ideally
100 * we'd just query the creation time the FS might provide, but
101 * unfortunately there's currently no sane API to query
102 * it. Hence let's implement this manually... */
104 /* Unfortunately there is is not fgetxattrat(), so we need to
105 * go via path here. :-( */
107 path
= strjoin(dir
, "/", fn
, NULL
);
111 if (path_getcrtime(path
, &crtime
) >= 0) {
112 if (crtime
< *realtime
)
117 static int journal_file_empty(int dir_fd
, const char *name
) {
118 _cleanup_close_
int fd
;
123 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 usec_t max_retention_usec
,
151 _cleanup_closedir_
DIR *d
= NULL
;
153 struct vacuum_info
*list
= NULL
;
154 unsigned n_list
= 0, i
;
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
];
162 if (max_use
<= 0 && max_retention_usec
<= 0)
165 if (max_retention_usec
> 0) {
166 retention_limit
= now(CLOCK_REALTIME
);
167 if (retention_limit
> max_retention_usec
)
168 retention_limit
-= max_retention_usec
;
170 max_retention_usec
= retention_limit
= 0;
173 d
= opendir(directory
);
182 unsigned long long seqnum
= 0, realtime
;
183 sd_id128_t seqnum_id
;
188 if (!de
&& errno
!= 0) {
196 if (fstatat(dirfd(d
), de
->d_name
, &st
, AT_SYMLINK_NOFOLLOW
) < 0)
199 if (!S_ISREG(st
.st_mode
))
202 q
= strlen(de
->d_name
);
204 if (endswith(de
->d_name
, ".journal")) {
206 /* Vacuum archived files */
208 if (q
< 1 + 32 + 1 + 16 + 1 + 16 + 8)
211 if (de
->d_name
[q
-8-16-1] != '-' ||
212 de
->d_name
[q
-8-16-1-16-1] != '-' ||
213 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)
243 if (de
->d_name
[q
-1-8-16-1] != '-' ||
244 de
->d_name
[q
-1-8-16-1-16-1] != '@')
247 p
= strdup(de
->d_name
);
253 if (sscanf(de
->d_name
+ q
-1-8-16-1-16, "%16llx-%16llx.journal~", &realtime
, &tmp
) != 2) {
260 /* We do not vacuum active files or unknown files! */
263 if (journal_file_empty(dirfd(d
), p
)) {
264 /* Always vacuum empty non-online files. */
266 uint64_t size
= 512UL * (uint64_t) st
.st_blocks
;
268 if (unlinkat(dirfd(d
), p
, 0) >= 0) {
269 log_full(verbose
? LOG_INFO
: LOG_DEBUG
, "Deleted empty archived journal %s/%s (%s).", directory
, p
, format_bytes(sbytes
, sizeof(sbytes
), size
));
271 } else if (errno
!= ENOENT
)
272 log_warning_errno(errno
, "Failed to delete empty archived journal %s/%s: %m", directory
, p
);
278 patch_realtime(directory
, p
, &st
, &realtime
);
280 if (!GREEDY_REALLOC(list
, n_allocated
, n_list
+ 1)) {
286 list
[n_list
].filename
= p
;
287 list
[n_list
].usage
= 512UL * (uint64_t) st
.st_blocks
;
288 list
[n_list
].seqnum
= seqnum
;
289 list
[n_list
].realtime
= realtime
;
290 list
[n_list
].seqnum_id
= seqnum_id
;
291 list
[n_list
].have_seqnum
= have_seqnum
;
293 sum
+= list
[n_list
].usage
;
298 qsort_safe(list
, n_list
, sizeof(struct vacuum_info
), vacuum_compare
);
300 for (i
= 0; i
< n_list
; i
++) {
301 if ((max_retention_usec
<= 0 || list
[i
].realtime
>= retention_limit
) &&
302 (max_use
<= 0 || sum
<= max_use
))
305 if (unlinkat(dirfd(d
), list
[i
].filename
, 0) >= 0) {
306 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
));
307 freed
+= list
[i
].usage
;
309 if (list
[i
].usage
< sum
)
310 sum
-= list
[i
].usage
;
314 } else if (errno
!= ENOENT
)
315 log_warning_errno(errno
, "Failed to delete archived journal %s/%s: %m", directory
, list
[i
].filename
);
318 if (oldest_usec
&& i
< n_list
&& (*oldest_usec
== 0 || list
[i
].realtime
< *oldest_usec
))
319 *oldest_usec
= list
[i
].realtime
;
322 for (i
= 0; i
< n_list
; i
++)
323 free(list
[i
].filename
);
326 log_full(verbose
? LOG_INFO
: LOG_DEBUG
, "Vacuuming done, freed %s of archived journals on disk.", format_bytes(sbytes
, sizeof(sbytes
), freed
));