]>
Commit | Line | Data |
---|---|---|
0284adc6 LP |
1 | /*** |
2 | This file is part of systemd. | |
3 | ||
4 | Copyright 2011 Lennart Poettering | |
5 | ||
6 | systemd is free software; you can redistribute it and/or modify it | |
7 | under the terms of the GNU Lesser General Public License as published by | |
8 | the Free Software Foundation; either version 2.1 of the License, or | |
9 | (at your option) any later version. | |
10 | ||
11 | systemd is distributed in the hope that it will be useful, but | |
12 | WITHOUT ANY WARRANTY; without even the implied warranty of | |
13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
14 | Lesser General Public License for more details. | |
15 | ||
16 | You should have received a copy of the GNU Lesser General Public License | |
17 | along with systemd; If not, see <http://www.gnu.org/licenses/>. | |
18 | ***/ | |
19 | ||
0284adc6 LP |
20 | #include <fcntl.h> |
21 | #include <sys/stat.h> | |
0284adc6 | 22 | #include <unistd.h> |
fb0951b0 | 23 | |
07630cea LP |
24 | #include "sd-id128.h" |
25 | ||
b5efdb8a | 26 | #include "alloc-util.h" |
a0956174 | 27 | #include "dirent-util.h" |
3ffd4af2 | 28 | #include "fd-util.h" |
0284adc6 LP |
29 | #include "journal-def.h" |
30 | #include "journal-file.h" | |
3ffd4af2 | 31 | #include "journal-vacuum.h" |
6bedfcbb | 32 | #include "parse-util.h" |
07630cea | 33 | #include "string-util.h" |
0284adc6 | 34 | #include "util.h" |
89a5a90c | 35 | #include "xattr-util.h" |
0284adc6 LP |
36 | |
37 | struct vacuum_info { | |
6c142648 | 38 | uint64_t usage; |
0284adc6 LP |
39 | char *filename; |
40 | ||
41 | uint64_t realtime; | |
2e14c544 | 42 | |
0284adc6 LP |
43 | sd_id128_t seqnum_id; |
44 | uint64_t seqnum; | |
0284adc6 LP |
45 | bool have_seqnum; |
46 | }; | |
47 | ||
48 | static int vacuum_compare(const void *_a, const void *_b) { | |
49 | const struct vacuum_info *a, *b; | |
50 | ||
51 | a = _a; | |
52 | b = _b; | |
53 | ||
54 | if (a->have_seqnum && b->have_seqnum && | |
55 | sd_id128_equal(a->seqnum_id, b->seqnum_id)) { | |
56 | if (a->seqnum < b->seqnum) | |
57 | return -1; | |
58 | else if (a->seqnum > b->seqnum) | |
59 | return 1; | |
60 | else | |
61 | return 0; | |
62 | } | |
63 | ||
64 | if (a->realtime < b->realtime) | |
65 | return -1; | |
66 | else if (a->realtime > b->realtime) | |
67 | return 1; | |
68 | else if (a->have_seqnum && b->have_seqnum) | |
69 | return memcmp(&a->seqnum_id, &b->seqnum_id, 16); | |
70 | else | |
71 | return strcmp(a->filename, b->filename); | |
72 | } | |
73 | ||
fb0951b0 | 74 | static void patch_realtime( |
2e14c544 | 75 | int fd, |
fb0951b0 LP |
76 | const char *fn, |
77 | const struct stat *st, | |
78 | unsigned long long *realtime) { | |
79 | ||
a7f7d1bd | 80 | usec_t x, crtime = 0; |
fb0951b0 LP |
81 | |
82 | /* The timestamp was determined by the file name, but let's | |
83 | * see if the file might actually be older than the file name | |
84 | * suggested... */ | |
85 | ||
2e14c544 | 86 | assert(fd >= 0); |
fb0951b0 LP |
87 | assert(fn); |
88 | assert(st); | |
89 | assert(realtime); | |
90 | ||
91 | x = timespec_load(&st->st_ctim); | |
3a43da28 | 92 | if (x > 0 && x != USEC_INFINITY && x < *realtime) |
fb0951b0 LP |
93 | *realtime = x; |
94 | ||
95 | x = timespec_load(&st->st_atim); | |
3a43da28 | 96 | if (x > 0 && x != USEC_INFINITY && x < *realtime) |
fb0951b0 LP |
97 | *realtime = x; |
98 | ||
99 | x = timespec_load(&st->st_mtim); | |
3a43da28 | 100 | if (x > 0 && x != USEC_INFINITY && x < *realtime) |
fb0951b0 LP |
101 | *realtime = x; |
102 | ||
fb0951b0 LP |
103 | /* Let's read the original creation time, if possible. Ideally |
104 | * we'd just query the creation time the FS might provide, but | |
105 | * unfortunately there's currently no sane API to query | |
106 | * it. Hence let's implement this manually... */ | |
107 | ||
2e14c544 | 108 | if (fd_getcrtime_at(fd, fn, &crtime, 0) >= 0) { |
4a4d89b6 | 109 | if (crtime < *realtime) |
fb0951b0 LP |
110 | *realtime = crtime; |
111 | } | |
fb0951b0 LP |
112 | } |
113 | ||
9d647740 | 114 | static int journal_file_empty(int dir_fd, const char *name) { |
48979861 | 115 | _cleanup_close_ int fd; |
332076b4 LP |
116 | struct stat st; |
117 | le64_t n_entries; | |
118 | ssize_t n; | |
9d647740 | 119 | |
7b5195e2 LP |
120 | fd = openat(dir_fd, name, O_RDONLY|O_CLOEXEC|O_NOFOLLOW|O_NONBLOCK|O_NOATIME); |
121 | if (fd < 0) { | |
122 | /* Maybe failed due to O_NOATIME and lack of privileges? */ | |
123 | fd = openat(dir_fd, name, O_RDONLY|O_CLOEXEC|O_NOFOLLOW|O_NONBLOCK); | |
124 | if (fd < 0) | |
125 | return -errno; | |
126 | } | |
9d647740 | 127 | |
332076b4 | 128 | if (fstat(fd, &st) < 0) |
9d647740 ZJS |
129 | return -errno; |
130 | ||
332076b4 LP |
131 | /* If an offline file doesn't even have a header we consider it empty */ |
132 | if (st.st_size < (off_t) sizeof(Header)) | |
133 | return 1; | |
134 | ||
135 | /* If the number of entries is empty, we consider it empty, too */ | |
136 | n = pread(fd, &n_entries, sizeof(n_entries), offsetof(Header, n_entries)); | |
137 | if (n < 0) | |
138 | return -errno; | |
139 | if (n != sizeof(n_entries)) | |
140 | return -EIO; | |
9d647740 | 141 | |
332076b4 | 142 | return le64toh(n_entries) <= 0; |
9d647740 ZJS |
143 | } |
144 | ||
fb0951b0 LP |
145 | int journal_directory_vacuum( |
146 | const char *directory, | |
147 | uint64_t max_use, | |
8580d1f7 | 148 | uint64_t n_max_files, |
fb0951b0 | 149 | usec_t max_retention_usec, |
dbd2a83f LP |
150 | usec_t *oldest_usec, |
151 | bool verbose) { | |
fb0951b0 | 152 | |
30cb029b | 153 | _cleanup_closedir_ DIR *d = NULL; |
0284adc6 | 154 | struct vacuum_info *list = NULL; |
8580d1f7 | 155 | unsigned n_list = 0, i, n_active_files = 0; |
30cb029b | 156 | size_t n_allocated = 0; |
289f910e | 157 | uint64_t sum = 0, freed = 0; |
fb0951b0 | 158 | usec_t retention_limit = 0; |
dbd2a83f | 159 | char sbytes[FORMAT_BYTES_MAX]; |
8580d1f7 LP |
160 | struct dirent *de; |
161 | int r; | |
0284adc6 LP |
162 | |
163 | assert(directory); | |
164 | ||
8580d1f7 | 165 | if (max_use <= 0 && max_retention_usec <= 0 && n_max_files <= 0) |
0284adc6 LP |
166 | return 0; |
167 | ||
fb0951b0 LP |
168 | if (max_retention_usec > 0) { |
169 | retention_limit = now(CLOCK_REALTIME); | |
170 | if (retention_limit > max_retention_usec) | |
171 | retention_limit -= max_retention_usec; | |
172 | else | |
173 | max_retention_usec = retention_limit = 0; | |
174 | } | |
175 | ||
0284adc6 LP |
176 | d = opendir(directory); |
177 | if (!d) | |
178 | return -errno; | |
179 | ||
8580d1f7 LP |
180 | FOREACH_DIRENT_ALL(de, d, r = -errno; goto finish) { |
181 | ||
0284adc6 | 182 | unsigned long long seqnum = 0, realtime; |
8580d1f7 | 183 | _cleanup_free_ char *p = NULL; |
0284adc6 LP |
184 | sd_id128_t seqnum_id; |
185 | bool have_seqnum; | |
8580d1f7 LP |
186 | uint64_t size; |
187 | struct stat st; | |
188 | size_t q; | |
0284adc6 | 189 | |
8580d1f7 LP |
190 | if (fstatat(dirfd(d), de->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0) { |
191 | log_debug_errno(errno, "Failed to stat file %s while vacuuming, ignoring: %m", de->d_name); | |
0284adc6 | 192 | continue; |
8580d1f7 | 193 | } |
0284adc6 LP |
194 | |
195 | if (!S_ISREG(st.st_mode)) | |
196 | continue; | |
197 | ||
198 | q = strlen(de->d_name); | |
199 | ||
200 | if (endswith(de->d_name, ".journal")) { | |
201 | ||
8580d1f7 LP |
202 | /* Vacuum archived files. Active files are |
203 | * left around */ | |
0284adc6 | 204 | |
8580d1f7 LP |
205 | if (q < 1 + 32 + 1 + 16 + 1 + 16 + 8) { |
206 | n_active_files++; | |
0284adc6 | 207 | continue; |
8580d1f7 | 208 | } |
0284adc6 LP |
209 | |
210 | if (de->d_name[q-8-16-1] != '-' || | |
211 | de->d_name[q-8-16-1-16-1] != '-' || | |
8580d1f7 LP |
212 | de->d_name[q-8-16-1-16-1-32-1] != '@') { |
213 | n_active_files++; | |
0284adc6 | 214 | continue; |
8580d1f7 | 215 | } |
0284adc6 LP |
216 | |
217 | p = strdup(de->d_name); | |
218 | if (!p) { | |
219 | r = -ENOMEM; | |
220 | goto finish; | |
221 | } | |
222 | ||
223 | de->d_name[q-8-16-1-16-1] = 0; | |
224 | if (sd_id128_from_string(de->d_name + q-8-16-1-16-1-32, &seqnum_id) < 0) { | |
8580d1f7 | 225 | n_active_files++; |
0284adc6 LP |
226 | continue; |
227 | } | |
228 | ||
229 | if (sscanf(de->d_name + q-8-16-1-16, "%16llx-%16llx.journal", &seqnum, &realtime) != 2) { | |
8580d1f7 | 230 | n_active_files++; |
0284adc6 LP |
231 | continue; |
232 | } | |
233 | ||
234 | have_seqnum = true; | |
235 | ||
236 | } else if (endswith(de->d_name, ".journal~")) { | |
237 | unsigned long long tmp; | |
238 | ||
239 | /* Vacuum corrupted files */ | |
240 | ||
8580d1f7 | 241 | if (q < 1 + 16 + 1 + 16 + 8 + 1) { |
313cefa1 | 242 | n_active_files++; |
0284adc6 | 243 | continue; |
8580d1f7 | 244 | } |
0284adc6 LP |
245 | |
246 | if (de->d_name[q-1-8-16-1] != '-' || | |
8580d1f7 | 247 | de->d_name[q-1-8-16-1-16-1] != '@') { |
313cefa1 | 248 | n_active_files++; |
0284adc6 | 249 | continue; |
8580d1f7 | 250 | } |
0284adc6 LP |
251 | |
252 | p = strdup(de->d_name); | |
253 | if (!p) { | |
254 | r = -ENOMEM; | |
255 | goto finish; | |
256 | } | |
257 | ||
258 | if (sscanf(de->d_name + q-1-8-16-1-16, "%16llx-%16llx.journal~", &realtime, &tmp) != 2) { | |
313cefa1 | 259 | n_active_files++; |
0284adc6 LP |
260 | continue; |
261 | } | |
262 | ||
263 | have_seqnum = false; | |
8580d1f7 LP |
264 | } else { |
265 | /* We do not vacuum unknown files! */ | |
266 | log_debug("Not vacuuming unknown file %s.", de->d_name); | |
0284adc6 | 267 | continue; |
8580d1f7 | 268 | } |
0284adc6 | 269 | |
8580d1f7 | 270 | size = 512UL * (uint64_t) st.st_blocks; |
9d647740 | 271 | |
8580d1f7 LP |
272 | r = journal_file_empty(dirfd(d), p); |
273 | if (r < 0) { | |
274 | log_debug_errno(r, "Failed check if %s is empty, ignoring: %m", p); | |
275 | continue; | |
276 | } | |
277 | if (r > 0) { | |
278 | /* Always vacuum empty non-online files. */ | |
289f910e ZJS |
279 | |
280 | if (unlinkat(dirfd(d), p, 0) >= 0) { | |
8580d1f7 LP |
281 | |
282 | log_full(verbose ? LOG_INFO : LOG_DEBUG, | |
283 | "Deleted empty archived journal %s/%s (%s).", directory, p, format_bytes(sbytes, sizeof(sbytes), size)); | |
284 | ||
289f910e ZJS |
285 | freed += size; |
286 | } else if (errno != ENOENT) | |
56f64d95 | 287 | log_warning_errno(errno, "Failed to delete empty archived journal %s/%s: %m", directory, p); |
289f910e | 288 | |
9d647740 ZJS |
289 | continue; |
290 | } | |
291 | ||
8580d1f7 | 292 | patch_realtime(dirfd(d), p, &st, &realtime); |
fb0951b0 | 293 | |
26d8ff04 | 294 | if (!GREEDY_REALLOC(list, n_allocated, n_list + 1)) { |
26d8ff04 LP |
295 | r = -ENOMEM; |
296 | goto finish; | |
297 | } | |
0284adc6 LP |
298 | |
299 | list[n_list].filename = p; | |
8580d1f7 | 300 | list[n_list].usage = size; |
0284adc6 LP |
301 | list[n_list].seqnum = seqnum; |
302 | list[n_list].realtime = realtime; | |
303 | list[n_list].seqnum_id = seqnum_id; | |
304 | list[n_list].have_seqnum = have_seqnum; | |
313cefa1 | 305 | n_list++; |
8580d1f7 LP |
306 | |
307 | p = NULL; | |
308 | sum += size; | |
0284adc6 LP |
309 | } |
310 | ||
7ff7394d | 311 | qsort_safe(list, n_list, sizeof(struct vacuum_info), vacuum_compare); |
0284adc6 | 312 | |
fb0951b0 | 313 | for (i = 0; i < n_list; i++) { |
8580d1f7 LP |
314 | unsigned left; |
315 | ||
316 | left = n_active_files + n_list - i; | |
317 | ||
fb0951b0 | 318 | if ((max_retention_usec <= 0 || list[i].realtime >= retention_limit) && |
8580d1f7 LP |
319 | (max_use <= 0 || sum <= max_use) && |
320 | (n_max_files <= 0 || left <= n_max_files)) | |
0284adc6 LP |
321 | break; |
322 | ||
323 | if (unlinkat(dirfd(d), list[i].filename, 0) >= 0) { | |
dbd2a83f | 324 | 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 | 325 | freed += list[i].usage; |
4fa25d62 | 326 | |
6c142648 | 327 | if (list[i].usage < sum) |
4fa25d62 LP |
328 | sum -= list[i].usage; |
329 | else | |
330 | sum = 0; | |
331 | ||
0284adc6 | 332 | } else if (errno != ENOENT) |
56f64d95 | 333 | log_warning_errno(errno, "Failed to delete archived journal %s/%s: %m", directory, list[i].filename); |
0284adc6 LP |
334 | } |
335 | ||
fb0951b0 LP |
336 | if (oldest_usec && i < n_list && (*oldest_usec == 0 || list[i].realtime < *oldest_usec)) |
337 | *oldest_usec = list[i].realtime; | |
338 | ||
8580d1f7 LP |
339 | r = 0; |
340 | ||
0284adc6 LP |
341 | finish: |
342 | for (i = 0; i < n_list; i++) | |
343 | free(list[i].filename); | |
0284adc6 LP |
344 | free(list); |
345 | ||
3cc44bf9 | 346 | log_full(verbose ? LOG_INFO : LOG_DEBUG, "Vacuuming done, freed %s of archived journals from %s.", format_bytes(sbytes, sizeof(sbytes), freed), directory); |
289f910e | 347 | |
0284adc6 LP |
348 | return r; |
349 | } |