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