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