]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/libsystemd/sd-journal/journal-vacuum.c
tree-wide: use -EBADF for fd initialization
[thirdparty/systemd.git] / src / libsystemd / sd-journal / journal-vacuum.c
1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3 #include <fcntl.h>
4 #include <sys/stat.h>
5 #include <unistd.h>
6
7 #include "sd-id128.h"
8
9 #include "alloc-util.h"
10 #include "dirent-util.h"
11 #include "fd-util.h"
12 #include "format-util.h"
13 #include "fs-util.h"
14 #include "journal-def.h"
15 #include "journal-file.h"
16 #include "journal-internal.h"
17 #include "journal-vacuum.h"
18 #include "sort-util.h"
19 #include "string-util.h"
20 #include "time-util.h"
21 #include "xattr-util.h"
22
23 struct vacuum_info {
24 uint64_t usage;
25 char *filename;
26
27 uint64_t realtime;
28
29 sd_id128_t seqnum_id;
30 uint64_t seqnum;
31 bool have_seqnum;
32 };
33
34 static int vacuum_compare(const struct vacuum_info *a, const struct vacuum_info *b) {
35 int r;
36
37 if (a->have_seqnum && b->have_seqnum &&
38 sd_id128_equal(a->seqnum_id, b->seqnum_id))
39 return CMP(a->seqnum, b->seqnum);
40
41 r = CMP(a->realtime, b->realtime);
42 if (r != 0)
43 return r;
44
45 if (a->have_seqnum && b->have_seqnum)
46 return memcmp(&a->seqnum_id, &b->seqnum_id, 16);
47
48 return strcmp(a->filename, b->filename);
49 }
50
51 static void patch_realtime(
52 int fd,
53 const char *fn,
54 const struct stat *st,
55 unsigned long long *realtime) {
56
57 usec_t x;
58
59 /* The timestamp was determined by the file name, but let's see if the file might actually be older
60 * than the file name suggested... */
61
62 assert(fd >= 0);
63 assert(fn);
64 assert(st);
65 assert(realtime);
66
67 x = timespec_load(&st->st_ctim);
68 if (timestamp_is_set(x) && x < *realtime)
69 *realtime = x;
70
71 x = timespec_load(&st->st_atim);
72 if (timestamp_is_set(x) && x < *realtime)
73 *realtime = x;
74
75 x = timespec_load(&st->st_mtim);
76 if (timestamp_is_set(x) && x < *realtime)
77 *realtime = x;
78
79 /* Let's read the original creation time, if possible. Ideally we'd just query the creation time the
80 * FS might provide, but unfortunately there's currently no sane API to query it. Hence let's
81 * implement this manually... */
82
83 if (fd_getcrtime_at(fd, fn, AT_SYMLINK_FOLLOW, &x) >= 0 && x < *realtime)
84 *realtime = x;
85 }
86
87 static int journal_file_empty(int dir_fd, const char *name) {
88 _cleanup_close_ int fd = -EBADF;
89 struct stat st;
90 le64_t n_entries;
91 ssize_t n;
92
93 fd = openat(dir_fd, name, O_RDONLY|O_CLOEXEC|O_NOFOLLOW|O_NONBLOCK|O_NOATIME);
94 if (fd < 0) {
95 /* Maybe failed due to O_NOATIME and lack of privileges? */
96 fd = openat(dir_fd, name, O_RDONLY|O_CLOEXEC|O_NOFOLLOW|O_NONBLOCK);
97 if (fd < 0)
98 return -errno;
99 }
100
101 if (fstat(fd, &st) < 0)
102 return -errno;
103
104 /* If an offline file doesn't even have a header we consider it empty */
105 if (st.st_size < (off_t) sizeof(Header))
106 return 1;
107
108 /* If the number of entries is empty, we consider it empty, too */
109 n = pread(fd, &n_entries, sizeof(n_entries), offsetof(Header, n_entries));
110 if (n < 0)
111 return -errno;
112 if (n != sizeof(n_entries))
113 return -EIO;
114
115 return le64toh(n_entries) <= 0;
116 }
117
118 int journal_directory_vacuum(
119 const char *directory,
120 uint64_t max_use,
121 uint64_t n_max_files,
122 usec_t max_retention_usec,
123 usec_t *oldest_usec,
124 bool verbose) {
125
126 uint64_t sum = 0, freed = 0, n_active_files = 0;
127 size_t n_list = 0, i;
128 _cleanup_closedir_ DIR *d = NULL;
129 struct vacuum_info *list = NULL;
130 usec_t retention_limit = 0;
131 int r;
132
133 assert(directory);
134
135 if (max_use <= 0 && max_retention_usec <= 0 && n_max_files <= 0)
136 return 0;
137
138 if (max_retention_usec > 0)
139 retention_limit = usec_sub_unsigned(now(CLOCK_REALTIME), max_retention_usec);
140
141 d = opendir(directory);
142 if (!d)
143 return -errno;
144
145 FOREACH_DIRENT_ALL(de, d, r = -errno; goto finish) {
146 unsigned long long seqnum = 0, realtime;
147 _cleanup_free_ char *p = NULL;
148 sd_id128_t seqnum_id;
149 bool have_seqnum;
150 uint64_t size;
151 struct stat st;
152 size_t q;
153
154 if (fstatat(dirfd(d), de->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0) {
155 log_debug_errno(errno, "Failed to stat file %s while vacuuming, ignoring: %m", de->d_name);
156 continue;
157 }
158
159 if (!S_ISREG(st.st_mode))
160 continue;
161
162 q = strlen(de->d_name);
163
164 if (endswith(de->d_name, ".journal")) {
165
166 /* Vacuum archived files. Active files are
167 * left around */
168
169 if (q < 1 + 32 + 1 + 16 + 1 + 16 + 8) {
170 n_active_files++;
171 continue;
172 }
173
174 if (de->d_name[q-8-16-1] != '-' ||
175 de->d_name[q-8-16-1-16-1] != '-' ||
176 de->d_name[q-8-16-1-16-1-32-1] != '@') {
177 n_active_files++;
178 continue;
179 }
180
181 p = strdup(de->d_name);
182 if (!p) {
183 r = -ENOMEM;
184 goto finish;
185 }
186
187 de->d_name[q-8-16-1-16-1] = 0;
188 if (sd_id128_from_string(de->d_name + q-8-16-1-16-1-32, &seqnum_id) < 0) {
189 n_active_files++;
190 continue;
191 }
192
193 if (sscanf(de->d_name + q-8-16-1-16, "%16llx-%16llx.journal", &seqnum, &realtime) != 2) {
194 n_active_files++;
195 continue;
196 }
197
198 have_seqnum = true;
199
200 } else if (endswith(de->d_name, ".journal~")) {
201 unsigned long long tmp;
202
203 /* seqnum_id won't be initialised before use below, so set to 0 */
204 seqnum_id = SD_ID128_NULL;
205
206 /* Vacuum corrupted files */
207
208 if (q < 1 + 16 + 1 + 16 + 8 + 1) {
209 n_active_files++;
210 continue;
211 }
212
213 if (de->d_name[q-1-8-16-1] != '-' ||
214 de->d_name[q-1-8-16-1-16-1] != '@') {
215 n_active_files++;
216 continue;
217 }
218
219 p = strdup(de->d_name);
220 if (!p) {
221 r = -ENOMEM;
222 goto finish;
223 }
224
225 if (sscanf(de->d_name + q-1-8-16-1-16, "%16llx-%16llx.journal~", &realtime, &tmp) != 2) {
226 n_active_files++;
227 continue;
228 }
229
230 have_seqnum = false;
231 } else {
232 /* We do not vacuum unknown files! */
233 log_debug("Not vacuuming unknown file %s.", de->d_name);
234 continue;
235 }
236
237 size = 512UL * (uint64_t) st.st_blocks;
238
239 r = journal_file_empty(dirfd(d), p);
240 if (r < 0) {
241 log_debug_errno(r, "Failed check if %s is empty, ignoring: %m", p);
242 continue;
243 }
244 if (r > 0) {
245 /* Always vacuum empty non-online files. */
246
247 r = unlinkat_deallocate(dirfd(d), p, 0);
248 if (r >= 0) {
249
250 log_full(verbose ? LOG_INFO : LOG_DEBUG,
251 "Deleted empty archived journal %s/%s (%s).", directory, p, FORMAT_BYTES(size));
252
253 freed += size;
254 } else if (r != -ENOENT)
255 log_ratelimit_warning_errno(r, JOURNAL_LOG_RATELIMIT,
256 "Failed to delete empty archived journal %s/%s: %m",
257 directory, p);
258
259 continue;
260 }
261
262 patch_realtime(dirfd(d), p, &st, &realtime);
263
264 if (!GREEDY_REALLOC(list, n_list + 1)) {
265 r = -ENOMEM;
266 goto finish;
267 }
268
269 list[n_list++] = (struct vacuum_info) {
270 .filename = TAKE_PTR(p),
271 .usage = size,
272 .seqnum = seqnum,
273 .realtime = realtime,
274 .seqnum_id = seqnum_id,
275 .have_seqnum = have_seqnum,
276 };
277
278 sum += size;
279 }
280
281 typesafe_qsort(list, n_list, vacuum_compare);
282
283 for (i = 0; i < n_list; i++) {
284 uint64_t left;
285
286 left = n_active_files + n_list - i;
287
288 if ((max_retention_usec <= 0 || list[i].realtime >= retention_limit) &&
289 (max_use <= 0 || sum <= max_use) &&
290 (n_max_files <= 0 || left <= n_max_files))
291 break;
292
293 r = unlinkat_deallocate(dirfd(d), list[i].filename, 0);
294 if (r >= 0) {
295 log_full(verbose ? LOG_INFO : LOG_DEBUG, "Deleted archived journal %s/%s (%s).",
296 directory, list[i].filename, FORMAT_BYTES(list[i].usage));
297 freed += list[i].usage;
298
299 if (list[i].usage < sum)
300 sum -= list[i].usage;
301 else
302 sum = 0;
303
304 } else if (r != -ENOENT)
305 log_ratelimit_warning_errno(r, JOURNAL_LOG_RATELIMIT,
306 "Failed to delete archived journal %s/%s: %m",
307 directory, list[i].filename);
308 }
309
310 if (oldest_usec && i < n_list && (*oldest_usec == 0 || list[i].realtime < *oldest_usec))
311 *oldest_usec = list[i].realtime;
312
313 r = 0;
314
315 finish:
316 for (i = 0; i < n_list; i++)
317 free(list[i].filename);
318 free(list);
319
320 log_full(verbose ? LOG_INFO : LOG_DEBUG, "Vacuuming done, freed %s of archived journals from %s.",
321 FORMAT_BYTES(freed), directory);
322
323 return r;
324 }