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