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