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