chiark / gitweb /
sd-dhcp-server: add RELEASE support
[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 #include <sys/xattr.h>
28
29 #include "journal-def.h"
30 #include "journal-file.h"
31 #include "journal-vacuum.h"
32 #include "sd-id128.h"
33 #include "util.h"
34
35 struct vacuum_info {
36         uint64_t usage;
37         char *filename;
38
39         uint64_t realtime;
40         sd_id128_t seqnum_id;
41         uint64_t seqnum;
42
43         bool have_seqnum;
44 };
45
46 static int vacuum_compare(const void *_a, const void *_b) {
47         const struct vacuum_info *a, *b;
48
49         a = _a;
50         b = _b;
51
52         if (a->have_seqnum && b->have_seqnum &&
53             sd_id128_equal(a->seqnum_id, b->seqnum_id)) {
54                 if (a->seqnum < b->seqnum)
55                         return -1;
56                 else if (a->seqnum > b->seqnum)
57                         return 1;
58                 else
59                         return 0;
60         }
61
62         if (a->realtime < b->realtime)
63                 return -1;
64         else if (a->realtime > b->realtime)
65                 return 1;
66         else if (a->have_seqnum && b->have_seqnum)
67                 return memcmp(&a->seqnum_id, &b->seqnum_id, 16);
68         else
69                 return strcmp(a->filename, b->filename);
70 }
71
72 static void patch_realtime(
73                 const char *dir,
74                 const char *fn,
75                 const struct stat *st,
76                 unsigned long long *realtime) {
77
78         usec_t x;
79         uint64_t crtime;
80         _cleanup_free_ const char *path = NULL;
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
86         assert(dir);
87         assert(fn);
88         assert(st);
89         assert(realtime);
90
91         x = timespec_load(&st->st_ctim);
92         if (x > 0 && x != (usec_t) -1 && x < *realtime)
93                 *realtime = x;
94
95         x = timespec_load(&st->st_atim);
96         if (x > 0 && x != (usec_t) -1 && x < *realtime)
97                 *realtime = x;
98
99         x = timespec_load(&st->st_mtim);
100         if (x > 0 && x != (usec_t) -1 && x < *realtime)
101                 *realtime = x;
102
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
108         /* Unfortunately there is is not fgetxattrat(), so we need to
109          * go via path here. :-( */
110
111         path = strjoin(dir, "/", fn, NULL);
112         if (!path)
113                 return;
114
115         if (getxattr(path, "user.crtime_usec", &crtime, sizeof(crtime)) == sizeof(crtime)) {
116                 crtime = le64toh(crtime);
117
118                 if (crtime > 0 && crtime != (uint64_t) -1 && crtime < *realtime)
119                         *realtime = crtime;
120         }
121 }
122
123 static int journal_file_empty(int dir_fd, const char *name) {
124         int r;
125         le64_t n_entries;
126         _cleanup_close_ int fd;
127
128         fd = openat(dir_fd, name, O_RDONLY|O_CLOEXEC|O_NOFOLLOW|O_NONBLOCK);
129         if (fd < 0)
130                 return -errno;
131
132         if (lseek(fd, offsetof(Header, n_entries), SEEK_SET) < 0)
133                 return -errno;
134
135         r = read(fd, &n_entries, sizeof(n_entries));
136         if (r != sizeof(n_entries))
137                 return r == 0 ? -EINVAL : -errno;
138
139         return le64toh(n_entries) == 0;
140 }
141
142 int journal_directory_vacuum(
143                 const char *directory,
144                 uint64_t max_use,
145                 usec_t max_retention_usec,
146                 usec_t *oldest_usec) {
147
148         _cleanup_closedir_ DIR *d = NULL;
149         int r = 0;
150         struct vacuum_info *list = NULL;
151         unsigned n_list = 0, i;
152         size_t n_allocated = 0;
153         uint64_t sum = 0, freed = 0;
154         usec_t retention_limit = 0;
155
156         assert(directory);
157
158         if (max_use <= 0 && max_retention_usec <= 0)
159                 return 0;
160
161         if (max_retention_usec > 0) {
162                 retention_limit = now(CLOCK_REALTIME);
163                 if (retention_limit > max_retention_usec)
164                         retention_limit -= max_retention_usec;
165                 else
166                         max_retention_usec = retention_limit = 0;
167         }
168
169         d = opendir(directory);
170         if (!d)
171                 return -errno;
172
173         for (;;) {
174                 struct dirent *de;
175                 size_t q;
176                 struct stat st;
177                 char *p;
178                 unsigned long long seqnum = 0, realtime;
179                 sd_id128_t seqnum_id;
180                 bool have_seqnum;
181
182                 errno = 0;
183                 de = readdir(d);
184                 if (!de && errno != 0) {
185                         r = -errno;
186                         goto finish;
187                 }
188
189                 if (!de)
190                         break;
191
192                 if (fstatat(dirfd(d), de->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0)
193                         continue;
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
202                         /* Vacuum archived files */
203
204                         if (q < 1 + 32 + 1 + 16 + 1 + 16 + 8)
205                                 continue;
206
207                         if (de->d_name[q-8-16-1] != '-' ||
208                             de->d_name[q-8-16-1-16-1] != '-' ||
209                             de->d_name[q-8-16-1-16-1-32-1] != '@')
210                                 continue;
211
212                         p = strdup(de->d_name);
213                         if (!p) {
214                                 r = -ENOMEM;
215                                 goto finish;
216                         }
217
218                         de->d_name[q-8-16-1-16-1] = 0;
219                         if (sd_id128_from_string(de->d_name + q-8-16-1-16-1-32, &seqnum_id) < 0) {
220                                 free(p);
221                                 continue;
222                         }
223
224                         if (sscanf(de->d_name + q-8-16-1-16, "%16llx-%16llx.journal", &seqnum, &realtime) != 2) {
225                                 free(p);
226                                 continue;
227                         }
228
229                         have_seqnum = true;
230
231                 } else if (endswith(de->d_name, ".journal~")) {
232                         unsigned long long tmp;
233
234                         /* Vacuum corrupted files */
235
236                         if (q < 1 + 16 + 1 + 16 + 8 + 1)
237                                 continue;
238
239                         if (de->d_name[q-1-8-16-1] != '-' ||
240                             de->d_name[q-1-8-16-1-16-1] != '@')
241                                 continue;
242
243                         p = strdup(de->d_name);
244                         if (!p) {
245                                 r = -ENOMEM;
246                                 goto finish;
247                         }
248
249                         if (sscanf(de->d_name + q-1-8-16-1-16, "%16llx-%16llx.journal~", &realtime, &tmp) != 2) {
250                                 free(p);
251                                 continue;
252                         }
253
254                         have_seqnum = false;
255                 } else
256                         /* We do not vacuum active files or unknown files! */
257                         continue;
258
259                 if (journal_file_empty(dirfd(d), p)) {
260                         /* Always vacuum empty non-online files. */
261
262                         uint64_t size = 512UL * (uint64_t) st.st_blocks;
263
264                         if (unlinkat(dirfd(d), p, 0) >= 0) {
265                                 log_info("Deleted empty journal %s/%s (%"PRIu64" bytes).",
266                                          directory, p, size);
267                                 freed += size;
268                         } else if (errno != ENOENT)
269                                 log_warning("Failed to delete %s/%s: %m", directory, p);
270
271                         free(p);
272
273                         continue;
274                 }
275
276                 patch_realtime(directory, p, &st, &realtime);
277
278                 GREEDY_REALLOC(list, n_allocated, n_list + 1);
279
280                 list[n_list].filename = p;
281                 list[n_list].usage = 512UL * (uint64_t) st.st_blocks;
282                 list[n_list].seqnum = seqnum;
283                 list[n_list].realtime = realtime;
284                 list[n_list].seqnum_id = seqnum_id;
285                 list[n_list].have_seqnum = have_seqnum;
286
287                 sum += list[n_list].usage;
288
289                 n_list ++;
290         }
291
292         qsort_safe(list, n_list, sizeof(struct vacuum_info), vacuum_compare);
293
294         for (i = 0; i < n_list; i++) {
295                 struct statvfs ss;
296
297                 if (fstatvfs(dirfd(d), &ss) < 0) {
298                         r = -errno;
299                         goto finish;
300                 }
301
302                 if ((max_retention_usec <= 0 || list[i].realtime >= retention_limit) &&
303                     (max_use <= 0 || sum <= max_use))
304                         break;
305
306                 if (unlinkat(dirfd(d), list[i].filename, 0) >= 0) {
307                         log_debug("Deleted archived journal %s/%s (%"PRIu64" bytes).",
308                                   directory, list[i].filename, list[i].usage);
309                         freed += list[i].usage;
310
311                         if (list[i].usage < sum)
312                                 sum -= list[i].usage;
313                         else
314                                 sum = 0;
315
316                 } else if (errno != ENOENT)
317                         log_warning("Failed to delete %s/%s: %m", directory, list[i].filename);
318         }
319
320         if (oldest_usec && i < n_list && (*oldest_usec == 0 || list[i].realtime < *oldest_usec))
321                 *oldest_usec = list[i].realtime;
322
323 finish:
324         for (i = 0; i < n_list; i++)
325                 free(list[i].filename);
326         free(list);
327
328         log_debug("Vacuuming done, freed %"PRIu64" bytes", freed);
329
330         return r;
331 }