chiark / gitweb /
dbf5d2261b9975da8910a05b9cad5fccaa376a3e
[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_INFINITY && x < *realtime)
93                 *realtime = x;
94
95         x = timespec_load(&st->st_atim);
96         if (x > 0 && x != USEC_INFINITY && x < *realtime)
97                 *realtime = x;
98
99         x = timespec_load(&st->st_mtim);
100         if (x > 0 && x != USEC_INFINITY && 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                 bool verbose) {
148
149         _cleanup_closedir_ DIR *d = NULL;
150         int r = 0;
151         struct vacuum_info *list = NULL;
152         unsigned n_list = 0, i;
153         size_t n_allocated = 0;
154         uint64_t sum = 0, freed = 0;
155         usec_t retention_limit = 0;
156         char sbytes[FORMAT_BYTES_MAX];
157
158         assert(directory);
159
160         if (max_use <= 0 && max_retention_usec <= 0)
161                 return 0;
162
163         if (max_retention_usec > 0) {
164                 retention_limit = now(CLOCK_REALTIME);
165                 if (retention_limit > max_retention_usec)
166                         retention_limit -= max_retention_usec;
167                 else
168                         max_retention_usec = retention_limit = 0;
169         }
170
171         d = opendir(directory);
172         if (!d)
173                 return -errno;
174
175         for (;;) {
176                 struct dirent *de;
177                 size_t q;
178                 struct stat st;
179                 char *p;
180                 unsigned long long seqnum = 0, realtime;
181                 sd_id128_t seqnum_id;
182                 bool have_seqnum;
183
184                 errno = 0;
185                 de = readdir(d);
186                 if (!de && errno != 0) {
187                         r = -errno;
188                         goto finish;
189                 }
190
191                 if (!de)
192                         break;
193
194                 if (fstatat(dirfd(d), de->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0)
195                         continue;
196
197                 if (!S_ISREG(st.st_mode))
198                         continue;
199
200                 q = strlen(de->d_name);
201
202                 if (endswith(de->d_name, ".journal")) {
203
204                         /* Vacuum archived files */
205
206                         if (q < 1 + 32 + 1 + 16 + 1 + 16 + 8)
207                                 continue;
208
209                         if (de->d_name[q-8-16-1] != '-' ||
210                             de->d_name[q-8-16-1-16-1] != '-' ||
211                             de->d_name[q-8-16-1-16-1-32-1] != '@')
212                                 continue;
213
214                         p = strdup(de->d_name);
215                         if (!p) {
216                                 r = -ENOMEM;
217                                 goto finish;
218                         }
219
220                         de->d_name[q-8-16-1-16-1] = 0;
221                         if (sd_id128_from_string(de->d_name + q-8-16-1-16-1-32, &seqnum_id) < 0) {
222                                 free(p);
223                                 continue;
224                         }
225
226                         if (sscanf(de->d_name + q-8-16-1-16, "%16llx-%16llx.journal", &seqnum, &realtime) != 2) {
227                                 free(p);
228                                 continue;
229                         }
230
231                         have_seqnum = true;
232
233                 } else if (endswith(de->d_name, ".journal~")) {
234                         unsigned long long tmp;
235
236                         /* Vacuum corrupted files */
237
238                         if (q < 1 + 16 + 1 + 16 + 8 + 1)
239                                 continue;
240
241                         if (de->d_name[q-1-8-16-1] != '-' ||
242                             de->d_name[q-1-8-16-1-16-1] != '@')
243                                 continue;
244
245                         p = strdup(de->d_name);
246                         if (!p) {
247                                 r = -ENOMEM;
248                                 goto finish;
249                         }
250
251                         if (sscanf(de->d_name + q-1-8-16-1-16, "%16llx-%16llx.journal~", &realtime, &tmp) != 2) {
252                                 free(p);
253                                 continue;
254                         }
255
256                         have_seqnum = false;
257                 } else
258                         /* We do not vacuum active files or unknown files! */
259                         continue;
260
261                 if (journal_file_empty(dirfd(d), p)) {
262                         /* Always vacuum empty non-online files. */
263
264                         uint64_t size = 512UL * (uint64_t) st.st_blocks;
265
266                         if (unlinkat(dirfd(d), p, 0) >= 0) {
267                                 log_full(verbose ? LOG_INFO : LOG_DEBUG, "Deleted empty archived journal %s/%s (%s).", directory, p, format_bytes(sbytes, sizeof(sbytes), size));
268                                 freed += size;
269                         } else if (errno != ENOENT)
270                                 log_warning("Failed to delete empty archived journal %s/%s: %m", directory, p);
271
272                         free(p);
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                 if ((max_retention_usec <= 0 || list[i].realtime >= retention_limit) &&
296                     (max_use <= 0 || sum <= max_use))
297                         break;
298
299                 if (unlinkat(dirfd(d), list[i].filename, 0) >= 0) {
300                         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));
301                         freed += list[i].usage;
302
303                         if (list[i].usage < sum)
304                                 sum -= list[i].usage;
305                         else
306                                 sum = 0;
307
308                 } else if (errno != ENOENT)
309                         log_warning("Failed to delete archived journal %s/%s: %m", directory, list[i].filename);
310         }
311
312         if (oldest_usec && i < n_list && (*oldest_usec == 0 || list[i].realtime < *oldest_usec))
313                 *oldest_usec = list[i].realtime;
314
315 finish:
316         for (i = 0; i < n_list; i++)
317                 free(list[i].filename);
318         free(list);
319
320         log_full(verbose ? LOG_INFO : LOG_DEBUG, "Vacuuming done, freed %s of archived journals on disk.", format_bytes(sbytes, sizeof(sbytes), freed));
321
322         return r;
323 }