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