chiark / gitweb /
journald: fix vacuuming of archived journals
[elogind.git] / src / journal / journal-vacuum.c
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
28 #ifdef HAVE_XATTR
29 #include <attr/xattr.h>
30 #endif
31
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 {
39         uint64_t usage;
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
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
131 static int journal_file_empty(int dir_fd, const char *name) {
132         int fd, r;
133         le64_t n_entries;
134
135         fd = openat(dir_fd, name, O_RDONLY|O_CLOEXEC|O_NOFOLLOW|O_NONBLOCK);
136         if (fd < 0)
137                 return -errno;
138
139         if (lseek(fd, offsetof(Header, n_entries), SEEK_SET) < 0)
140                 return -errno;
141
142         r = read(fd, &n_entries, sizeof(n_entries));
143         if (r != sizeof(n_entries))
144                 return r == 0 ? -EINVAL : -errno;
145
146         return le64toh(n_entries) == 0;
147 }
148
149 int journal_directory_vacuum(
150                 const char *directory,
151                 uint64_t max_use,
152                 uint64_t min_free,
153                 usec_t max_retention_usec,
154                 usec_t *oldest_usec) {
155
156         _cleanup_closedir_ DIR *d = NULL;
157         int r = 0;
158         struct vacuum_info *list = NULL;
159         unsigned n_list = 0, i;
160         size_t n_allocated = 0;
161         uint64_t sum = 0;
162         usec_t retention_limit = 0;
163
164         assert(directory);
165
166         if (max_use <= 0 && min_free <= 0 && max_retention_usec <= 0)
167                 return 0;
168
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
177         d = opendir(directory);
178         if (!d)
179                 return -errno;
180
181         for (;;) {
182                 int k;
183                 struct dirent *de;
184                 union dirent_storage buf;
185                 size_t q;
186                 struct stat st;
187                 char *p;
188                 unsigned long long seqnum = 0, realtime;
189                 sd_id128_t seqnum_id;
190                 bool have_seqnum;
191
192                 k = readdir_r(d, &buf.de, &de);
193                 if (k != 0) {
194                         r = -k;
195                         goto finish;
196                 }
197
198                 if (!de)
199                         break;
200
201                 if (fstatat(dirfd(d), de->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0)
202                         continue;
203
204                 if (!S_ISREG(st.st_mode))
205                         continue;
206
207                 q = strlen(de->d_name);
208
209                 if (endswith(de->d_name, ".journal")) {
210
211                         /* Vacuum archived files */
212
213                         if (q < 1 + 32 + 1 + 16 + 1 + 16 + 8)
214                                 continue;
215
216                         if (de->d_name[q-8-16-1] != '-' ||
217                             de->d_name[q-8-16-1-16-1] != '-' ||
218                             de->d_name[q-8-16-1-16-1-32-1] != '@')
219                                 continue;
220
221                         p = strdup(de->d_name);
222                         if (!p) {
223                                 r = -ENOMEM;
224                                 goto finish;
225                         }
226
227                         de->d_name[q-8-16-1-16-1] = 0;
228                         if (sd_id128_from_string(de->d_name + q-8-16-1-16-1-32, &seqnum_id) < 0) {
229                                 free(p);
230                                 continue;
231                         }
232
233                         if (sscanf(de->d_name + q-8-16-1-16, "%16llx-%16llx.journal", &seqnum, &realtime) != 2) {
234                                 free(p);
235                                 continue;
236                         }
237
238                         have_seqnum = true;
239
240                 } else if (endswith(de->d_name, ".journal~")) {
241                         unsigned long long tmp;
242
243                         /* Vacuum corrupted files */
244
245                         if (q < 1 + 16 + 1 + 16 + 8 + 1)
246                                 continue;
247
248                         if (de->d_name[q-1-8-16-1] != '-' ||
249                             de->d_name[q-1-8-16-1-16-1] != '@')
250                                 continue;
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) {
259                                 free(p);
260                                 continue;
261                         }
262
263                         have_seqnum = false;
264                 } else
265                         /* We do not vacuum active files or unknown files! */
266                         continue;
267
268                 if (journal_file_empty(dirfd(d), p)) {
269
270                         /* Always vacuum empty non-online files. */
271
272                         if (unlinkat(dirfd(d), p, 0) >= 0)
273                                 log_debug("Deleted empty journal %s/%s.", directory, p);
274                         else if (errno != ENOENT)
275                                 log_warning("Failed to delete %s/%s: %m", directory, p);
276                         continue;
277                 }
278
279                 patch_realtime(directory, p, &st, &realtime);
280
281                 GREEDY_REALLOC(list, n_allocated, n_list + 1);
282
283                 list[n_list].filename = p;
284                 list[n_list].usage = 512UL * (uint64_t) st.st_blocks;
285                 list[n_list].seqnum = seqnum;
286                 list[n_list].realtime = realtime;
287                 list[n_list].seqnum_id = seqnum_id;
288                 list[n_list].have_seqnum = have_seqnum;
289
290                 sum += list[n_list].usage;
291
292                 n_list ++;
293         }
294
295         if (n_list > 0)
296                 qsort(list, n_list, sizeof(struct vacuum_info), vacuum_compare);
297
298         for (i = 0; i < n_list; i++) {
299                 struct statvfs ss;
300
301                 if (fstatvfs(dirfd(d), &ss) < 0) {
302                         r = -errno;
303                         goto finish;
304                 }
305
306                 if ((max_retention_usec <= 0 || list[i].realtime >= retention_limit) &&
307                     (max_use <= 0 || sum <= max_use) &&
308                     (min_free <= 0 || (uint64_t) ss.f_bavail * (uint64_t) ss.f_bsize >= min_free))
309                         break;
310
311                 if (unlinkat(dirfd(d), list[i].filename, 0) >= 0) {
312                         log_debug("Deleted archived journal %s/%s.", directory, list[i].filename);
313
314                         if (list[i].usage < sum)
315                                 sum -= list[i].usage;
316                         else
317                                 sum = 0;
318
319                 } else if (errno != ENOENT)
320                         log_warning("Failed to delete %s/%s: %m", directory, list[i].filename);
321         }
322
323         if (oldest_usec && i < n_list && (*oldest_usec == 0 || list[i].realtime < *oldest_usec))
324                 *oldest_usec = list[i].realtime;
325
326 finish:
327         for (i = 0; i < n_list; i++)
328                 free(list[i].filename);
329         free(list);
330
331         return r;
332 }